Fix Ranking code step1
This commit is contained in:
300
rog/views.py
300
rog/views.py
@ -23,7 +23,7 @@ import uuid
|
||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from django.db.models import F,Sum
|
||||
from rest_framework import viewsets, permissions, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
@ -853,6 +853,48 @@ class LoginAPI(generics.GenericAPIView):
|
||||
serializer_class = LoginUserSerializer
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
logger.info(f"Login attempt for identifier: {request.data.get('identifier', 'identifier not provided')}")
|
||||
logger.debug(f"Request data: {request.data}")
|
||||
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.validated_data
|
||||
logger.info(f"User {user.email} logged in successfully")
|
||||
|
||||
# ユーザー情報をシリアライズ
|
||||
user_data = UserSerializer(user, context=self.get_serializer_context()).data
|
||||
|
||||
# 認証トークンを生成
|
||||
token = AuthToken.objects.create(user)[1]
|
||||
|
||||
return Response({
|
||||
"user": user_data,
|
||||
"token": token
|
||||
})
|
||||
|
||||
except serializers.ValidationError as e:
|
||||
logger.error(f"Login failed for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||||
logger.error(f"Serializer errors: {serializer.errors}")
|
||||
|
||||
error_msg = serializer.errors.get('non_field_errors', ['ログインに失敗しました。'])[0]
|
||||
return Response({
|
||||
"error": error_msg,
|
||||
"details": serializer.errors
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during login for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
return Response({
|
||||
"error": "予期せぬエラーが発生しました。",
|
||||
"details": str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
|
||||
def post_old(self, request, *args, **kwargs):
|
||||
logger.info(f"Login attempt for user: {request.data.get('email', 'email not provided')}")
|
||||
logger.debug(f"Request data: {request.data}")
|
||||
|
||||
@ -3428,3 +3470,259 @@ def update_goal_time(request):
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
|
||||
|
||||
def get_team_status(last_checkin_time, goal_time, event_end_time):
|
||||
"""
|
||||
チームの状態を判定する
|
||||
"""
|
||||
now = timezone.now()
|
||||
|
||||
if goal_time:
|
||||
return "ゴール"
|
||||
|
||||
if not last_checkin_time:
|
||||
if now > event_end_time + timedelta(minutes=30):
|
||||
return "棄権"
|
||||
return "競技中"
|
||||
|
||||
# 最終チェックインから30分以上経過
|
||||
if now > last_checkin_time + timedelta(minutes=30):
|
||||
return "棄権"
|
||||
|
||||
return "競技中"
|
||||
|
||||
def calculate_late_points(goal_time, event_end_time):
|
||||
"""遅刻による減点を計算する"""
|
||||
if not goal_time or not event_end_time:
|
||||
return 0
|
||||
|
||||
minutes_late = max(0, int((goal_time - event_end_time).total_seconds() / 60))
|
||||
return minutes_late * -50
|
||||
|
||||
def is_disqualified(start_time, goal_time, duration):
|
||||
"""失格判定を行う"""
|
||||
if not goal_time or not start_time or not duration:
|
||||
return False # ゴール時間がない場合は失格としない(競技中の可能性)
|
||||
|
||||
# duration(timedelta)に15分を加算
|
||||
max_time = start_time + duration + timedelta(minutes=15)
|
||||
return goal_time > max_time
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_ranking(request, event_code, category_name):
|
||||
"""特定のイベントとクラスのランキングを取得する"""
|
||||
try:
|
||||
# イベントの情報を取得
|
||||
event = NewEvent2.objects.get(event_name=event_code)
|
||||
|
||||
# 有効なエントリーを取得
|
||||
entries = Entry.objects.filter(
|
||||
event=event,
|
||||
category__category_name=category_name,
|
||||
is_active=True
|
||||
).select_related('team', 'category')
|
||||
|
||||
rankings = []
|
||||
disqualified = [] # 失格チームのリスト
|
||||
|
||||
for entry in entries:
|
||||
# チェックインポイントを集計
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).aggregate(
|
||||
total_points=Sum('points')
|
||||
)
|
||||
|
||||
# 最後のチェックイン時刻を取得
|
||||
last_checkin = GpsCheckin.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).order_by('-create_at').first()
|
||||
|
||||
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||||
|
||||
# ゴール時間を取得 (最も早いゴール時間)
|
||||
goal_record = GoalImages.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).order_by('goaltime').first()
|
||||
|
||||
goal_time = goal_record.goaltime if goal_record else None
|
||||
total_points = checkins['total_points'] or 0
|
||||
|
||||
# イベントの終了予定時刻を計算
|
||||
expected_end_time = event.start_datetime + entry.category.duration
|
||||
|
||||
# チーム状態の判定
|
||||
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||||
|
||||
# 失格判定
|
||||
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||||
disqualified.append({
|
||||
'team_name': entry.team.team_name,
|
||||
'zekken_number': entry.zekken_number,
|
||||
'point': total_points,
|
||||
'late_point': 0,
|
||||
'goal_time': goal_time,
|
||||
'reason': '制限時間超過',
|
||||
'status': team_status
|
||||
})
|
||||
continue
|
||||
|
||||
# 遅刻減点を計算
|
||||
late_points = calculate_late_points(goal_time, expected_end_time)
|
||||
|
||||
rankings.append({
|
||||
'team_name': entry.team.team_name,
|
||||
'zekken_number': entry.zekken_number,
|
||||
'point': total_points,
|
||||
'late_point': abs(late_points),
|
||||
'final_point': total_points + late_points,
|
||||
'goal_time': goal_time,
|
||||
'status': team_status,
|
||||
'last_checkin': last_checkin_time
|
||||
})
|
||||
|
||||
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||||
# 棄権チームを最後に
|
||||
rankings.sort(key=lambda x: (
|
||||
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||||
-x['final_point'], # 得点の高い順
|
||||
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||||
))
|
||||
|
||||
# 有効なランキングと失格チームを結合
|
||||
final_rankings = {
|
||||
'rankings': rankings,
|
||||
'disqualified': disqualified
|
||||
}
|
||||
|
||||
return Response(final_rankings)
|
||||
|
||||
except NewEvent2.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Specified event not found"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_ranking: {str(e)}", exc_info=True)
|
||||
return Response(
|
||||
{"error": str(e)},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
@api_view(['GET'])
|
||||
def all_ranking_top3(request, event_code):
|
||||
"""特定のイベントの全クラスのTOP3を取得する"""
|
||||
try:
|
||||
# イベントの情報を取得
|
||||
event = NewEvent2.objects.get(event_name=event_code)
|
||||
|
||||
# イベントの有効なカテゴリーを取得
|
||||
categories = NewCategory.objects.filter(
|
||||
entry__event=event,
|
||||
entry__is_active=True
|
||||
).distinct()
|
||||
|
||||
rankings = {}
|
||||
for category in categories:
|
||||
# カテゴリーごとのエントリーを取得
|
||||
entries = Entry.objects.filter(
|
||||
event=event,
|
||||
category=category,
|
||||
is_active=True
|
||||
).select_related('team')
|
||||
|
||||
category_rankings = []
|
||||
disqualified = [] # カテゴリーごとの失格チーム
|
||||
|
||||
for entry in entries:
|
||||
# チェックインポイントを集計
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).aggregate(
|
||||
total_points=Sum('points')
|
||||
)
|
||||
|
||||
# 最後のチェックイン時刻を取得
|
||||
last_checkin = GpsCheckin.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).order_by('-create_at').first()
|
||||
|
||||
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||||
|
||||
# ゴール時間を取得
|
||||
goal_record = GoalImages.objects.filter(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event_code
|
||||
).order_by('goaltime').first()
|
||||
|
||||
goal_time = goal_record.goaltime if goal_record else None
|
||||
total_points = checkins['total_points'] or 0
|
||||
|
||||
# イベントの終了予定時刻を計算
|
||||
expected_end_time = event.start_datetime + category.duration
|
||||
|
||||
# チーム状態の判定
|
||||
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||||
|
||||
# 失格判定
|
||||
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||||
disqualified.append({
|
||||
'team_name': entry.team.team_name,
|
||||
'zekken_number': entry.zekken_number,
|
||||
'point': total_points,
|
||||
'late_point': 0,
|
||||
'goal_time': goal_time,
|
||||
'reason': '制限時間超過',
|
||||
'status': team_status
|
||||
})
|
||||
continue
|
||||
|
||||
# 遅刻減点を計算
|
||||
late_points = calculate_late_points(goal_time, expected_end_time)
|
||||
|
||||
category_rankings.append({
|
||||
'team_name': entry.team.team_name,
|
||||
'zekken_number': entry.zekken_number,
|
||||
'point': total_points,
|
||||
'late_point': abs(late_points),
|
||||
'final_point': total_points + late_points,
|
||||
'goal_time': goal_time,
|
||||
'status': team_status,
|
||||
'last_checkin': last_checkin_time
|
||||
})
|
||||
|
||||
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||||
# 棄権チームを最後に
|
||||
category_rankings.sort(key=lambda x: (
|
||||
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||||
-x['final_point'], # 得点の高い順
|
||||
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||||
))
|
||||
|
||||
# TOP3のみを保持(棄権を除く)
|
||||
top_rankings = [r for r in category_rankings if r['status'] != '棄権'][:3]
|
||||
|
||||
rankings[category.category_name] = {
|
||||
'rankings': top_rankings,
|
||||
'disqualified': disqualified
|
||||
}
|
||||
|
||||
return Response(rankings)
|
||||
|
||||
except NewEvent2.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Specified event not found"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in all_ranking_top3: {str(e)}", exc_info=True)
|
||||
return Response(
|
||||
{"error": str(e)},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
Reference in New Issue
Block a user