almost finish migrate new circumstances

This commit is contained in:
2025-08-24 19:44:36 +09:00
parent 1ba305641e
commit fe5a044c82
67 changed files with 1194889 additions and 467 deletions

View 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)