almost finish migrate new circumstances
This commit is contained in:
352
rog/views_apis/api_admin_validation.py
Normal file
352
rog/views_apis/api_admin_validation.py
Normal file
@ -0,0 +1,352 @@
|
||||
"""
|
||||
通過審査管理画面用API
|
||||
参加者全体の得点とクラス別ランキング表示機能
|
||||
"""
|
||||
|
||||
import logging
|
||||
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.models import Sum, Q, Count
|
||||
from django.db import models
|
||||
|
||||
from rog.models import NewEvent2, Entry, GpsCheckin, NewCategory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_event_participants_ranking(request):
|
||||
"""
|
||||
イベント参加者全体のクラス別得点ランキング取得
|
||||
|
||||
GET /api/event-participants-ranking/?event_code=FC岐阜
|
||||
"""
|
||||
try:
|
||||
event_code = request.GET.get('event_code')
|
||||
|
||||
if not event_code:
|
||||
return Response({
|
||||
'error': 'event_code parameter is required'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# イベントの検索(完全一致を優先)
|
||||
event = None
|
||||
if event_code:
|
||||
# まず完全一致でイベント名検索
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
# 次にイベントコードで検索
|
||||
event = NewEvent2.objects.filter(event_code=event_code).first()
|
||||
|
||||
if not event:
|
||||
return Response({
|
||||
'error': 'Event not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# イベント参加者の取得と得点計算
|
||||
entries = Entry.objects.filter(
|
||||
event=event,
|
||||
is_active=True
|
||||
).select_related('category', 'team').prefetch_related('team__members')
|
||||
|
||||
ranking_data = []
|
||||
|
||||
for entry in entries:
|
||||
# このエントリーのチェックイン記録を取得
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=str(entry.zekken_number),
|
||||
event_code=event_code
|
||||
)
|
||||
|
||||
# 得点計算
|
||||
total_points = 0
|
||||
cp_points = 0
|
||||
buy_points = 0
|
||||
confirmed_points = 0 # 確定済み得点
|
||||
unconfirmed_points = 0 # 未確定得点
|
||||
late_penalty = 0
|
||||
|
||||
for checkin in checkins:
|
||||
if checkin.points:
|
||||
if checkin.validate_location: # 確定済み
|
||||
confirmed_points += checkin.points
|
||||
if checkin.buy_flag:
|
||||
buy_points += checkin.points
|
||||
else:
|
||||
cp_points += checkin.points
|
||||
else: # 未確定
|
||||
unconfirmed_points += checkin.points
|
||||
|
||||
if checkin.late_point:
|
||||
late_penalty += checkin.late_point
|
||||
|
||||
total_points = confirmed_points - late_penalty
|
||||
|
||||
# チェックイン確定状況
|
||||
total_checkins = checkins.count()
|
||||
confirmed_checkins = checkins.filter(validate_location=True).count()
|
||||
confirmation_rate = (confirmed_checkins / total_checkins * 100) if total_checkins > 0 else 0
|
||||
|
||||
# チームメンバー情報
|
||||
team_members = []
|
||||
if entry.team and entry.team.members.exists():
|
||||
team_members = [
|
||||
{
|
||||
'name': f"{member.user.firstname} {member.user.lastname}" if member.user else member.name,
|
||||
'age': member.age if hasattr(member, 'age') else None
|
||||
}
|
||||
for member in entry.team.members.all()
|
||||
]
|
||||
|
||||
ranking_data.append({
|
||||
'rank': 0, # 後で設定
|
||||
'zekken_number': entry.zekken_number,
|
||||
'zekken_label': entry.zekken_label or f"{entry.zekken_number}",
|
||||
'team_name': entry.team.team_name if entry.team else "チーム名不明",
|
||||
'category': {
|
||||
'name': entry.category.category_name,
|
||||
'class_name': entry.category.category_name # class_nameプロパティがない場合はcategory_nameを使用
|
||||
},
|
||||
'members': team_members,
|
||||
'points': {
|
||||
'total': total_points,
|
||||
'cp_points': cp_points,
|
||||
'buy_points': buy_points,
|
||||
'confirmed_points': confirmed_points,
|
||||
'unconfirmed_points': unconfirmed_points,
|
||||
'late_penalty': late_penalty
|
||||
},
|
||||
'checkin_status': {
|
||||
'total_checkins': total_checkins,
|
||||
'confirmed_checkins': confirmed_checkins,
|
||||
'unconfirmed_checkins': total_checkins - confirmed_checkins,
|
||||
'confirmation_rate': round(confirmation_rate, 1)
|
||||
},
|
||||
'entry_status': {
|
||||
'has_participated': entry.hasParticipated,
|
||||
'has_goaled': entry.hasGoaled
|
||||
}
|
||||
})
|
||||
|
||||
# クラス別にソートしてランキング設定
|
||||
ranking_data.sort(key=lambda x: (x['category']['class_name'], -x['points']['total']))
|
||||
|
||||
# クラス別ランキングの設定
|
||||
current_class = None
|
||||
current_rank = 0
|
||||
for i, item in enumerate(ranking_data):
|
||||
if item['category']['class_name'] != current_class:
|
||||
current_class = item['category']['class_name']
|
||||
current_rank = 1
|
||||
else:
|
||||
current_rank += 1
|
||||
item['rank'] = current_rank
|
||||
item['class_rank'] = current_rank
|
||||
|
||||
# クラス別にグループ化
|
||||
classes_ranking = {}
|
||||
for item in ranking_data:
|
||||
class_name = item['category']['class_name']
|
||||
if class_name not in classes_ranking:
|
||||
classes_ranking[class_name] = []
|
||||
classes_ranking[class_name].append(item)
|
||||
|
||||
return Response({
|
||||
'status': 'success',
|
||||
'event': {
|
||||
'event_code': event_code,
|
||||
'event_name': event.event_name,
|
||||
'total_participants': len(ranking_data)
|
||||
},
|
||||
'classes_ranking': classes_ranking,
|
||||
'all_participants': ranking_data,
|
||||
'participants': ranking_data # JavaScript互換性のため
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_event_participants_ranking: {str(e)}")
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_participant_validation_details(request):
|
||||
"""
|
||||
参加者の通過情報詳細と確定状況の取得
|
||||
|
||||
GET /api/participant-validation-details/?event_code=FC岐阜&zekken_number=123
|
||||
"""
|
||||
try:
|
||||
event_code = request.GET.get('event_code')
|
||||
zekken_number = request.GET.get('zekken_number')
|
||||
|
||||
if not all([event_code, zekken_number]):
|
||||
return Response({
|
||||
'error': 'event_code and zekken_number parameters 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': 'Participant not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# チェックイン記録の取得
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=str(zekken_number),
|
||||
event_code=event_code
|
||||
).order_by('path_order')
|
||||
|
||||
checkin_details = []
|
||||
for checkin in checkins:
|
||||
checkin_details.append({
|
||||
'id': checkin.id,
|
||||
'path_order': checkin.path_order,
|
||||
'cp_number': checkin.cp_number,
|
||||
'checkin_time': checkin.create_at.isoformat() if checkin.create_at else None,
|
||||
'image_url': checkin.image_address,
|
||||
'gps_location': {
|
||||
'latitude': checkin.lattitude,
|
||||
'longitude': checkin.longitude
|
||||
} if checkin.lattitude and checkin.longitude else None,
|
||||
'validation': {
|
||||
'is_confirmed': checkin.validate_location,
|
||||
'buy_flag': checkin.buy_flag,
|
||||
'points': checkin.points or 0
|
||||
},
|
||||
'metadata': {
|
||||
'create_user': checkin.create_user,
|
||||
'update_user': checkin.update_user,
|
||||
'update_time': checkin.update_at.isoformat() if checkin.update_at else None
|
||||
}
|
||||
})
|
||||
|
||||
# 統計情報
|
||||
stats = {
|
||||
'total_checkins': len(checkin_details),
|
||||
'confirmed_checkins': sum(1 for c in checkin_details if c['validation']['is_confirmed']),
|
||||
'unconfirmed_checkins': sum(1 for c in checkin_details if not c['validation']['is_confirmed']),
|
||||
'total_points': sum(c['validation']['points'] for c in checkin_details if c['validation']['is_confirmed']),
|
||||
'potential_points': sum(c['validation']['points'] for c in checkin_details)
|
||||
}
|
||||
|
||||
return Response({
|
||||
'status': 'success',
|
||||
'participant': {
|
||||
'zekken_number': entry.zekken_number,
|
||||
'team_name': entry.team_name,
|
||||
'category': entry.category.name,
|
||||
'class_name': entry.class_name
|
||||
},
|
||||
'statistics': stats,
|
||||
'checkins': checkin_details
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_participant_validation_details: {str(e)}")
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_event_zekken_list(request):
|
||||
"""
|
||||
イベントのゼッケン番号リスト取得(ALLオプション付き)
|
||||
|
||||
POST /api/event-zekken-list/
|
||||
{
|
||||
"event_code": "FC岐阜"
|
||||
}
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
data = json.loads(request.body)
|
||||
event_code = data.get('event_code')
|
||||
|
||||
if event_code is None:
|
||||
return Response({
|
||||
'error': 'event_code parameter is required'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# イベントの確認 - event_code=Noneの場合の処理を追加
|
||||
try:
|
||||
if event_code == '' or event_code is None:
|
||||
# event_code=Noneまたは空文字列の場合
|
||||
event = NewEvent2.objects.filter(event_code=None).first()
|
||||
else:
|
||||
# まずevent_nameで正確な検索を試す
|
||||
try:
|
||||
event = NewEvent2.objects.get(event_name=event_code)
|
||||
except NewEvent2.DoesNotExist:
|
||||
# event_nameで見つからない場合はevent_codeで検索
|
||||
event = NewEvent2.objects.get(event_code=event_code)
|
||||
|
||||
if not event:
|
||||
return Response({
|
||||
'error': 'Event not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
except NewEvent2.DoesNotExist:
|
||||
return Response({
|
||||
'error': 'Event not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 参加エントリーの取得
|
||||
entries = Entry.objects.filter(
|
||||
event=event,
|
||||
is_active=True
|
||||
).order_by('zekken_number')
|
||||
|
||||
zekken_list = []
|
||||
|
||||
# ALLオプションを最初に追加
|
||||
zekken_list.append({
|
||||
'value': 'ALL',
|
||||
'label': 'ALL(全参加者)',
|
||||
'team_name': '全参加者表示',
|
||||
'category': '全クラス'
|
||||
})
|
||||
|
||||
# 各参加者のゼッケン番号を追加
|
||||
for entry in entries:
|
||||
team_name = entry.team.team_name if entry.team else 'チーム名未設定'
|
||||
category_name = entry.category.category_name if entry.category else 'クラス未設定'
|
||||
|
||||
zekken_list.append({
|
||||
'value': str(entry.zekken_number),
|
||||
'label': f"{entry.zekken_number} - {team_name}",
|
||||
'team_name': team_name,
|
||||
'category': category_name
|
||||
})
|
||||
|
||||
return Response({
|
||||
'status': 'success',
|
||||
'event_code': event_code,
|
||||
'zekken_options': zekken_list
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_event_zekken_list: {str(e)}")
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user