Fix Ranking code step1

This commit is contained in:
2024-11-12 07:19:18 +09:00
parent 19f12652b9
commit fccc55cf18
9 changed files with 4094 additions and 4 deletions

View File

@ -27,6 +27,7 @@ from django.shortcuts import get_object_or_404
from django.utils import timezone
from datetime import datetime, date
logger = logging.getLogger(__name__)
class LocationCatSerializer(serializers.ModelSerializer):
@ -876,3 +877,37 @@ class UserLastGoalTimeSerializer(serializers.Serializer):
user_email = serializers.EmailField()
last_goal_time = serializers.DateTimeField()
class LoginUserSerializer(serializers.Serializer):
identifier = serializers.CharField(required=True) # メールアドレスまたはゼッケン番号
password = serializers.CharField(required=True)
def validate(self, data):
identifier = data.get('identifier')
password = data.get('password')
if not identifier or not password:
raise serializers.ValidationError('認証情報を入力してください。')
# ゼッケン番号かメールアドレスかを判定
if '@' in identifier:
# メールアドレスの場合
user = authenticate(username=identifier, password=password)
else:
# ゼッケン番号の場合
try:
# ゼッケン番号からユーザーを検索
user = CustomUser.objects.filter(zekken_number=identifier).first()
if user:
# パスワード認証
if not user.check_password(password):
user = None
except ValueError:
user = None
if user and user.is_active:
return user
elif user and not user.is_active:
raise serializers.ValidationError('アカウントが有効化されていません。')
else:
raise serializers.ValidationError('認証情報が正しくありません。')

View File

@ -1,7 +1,7 @@
from sys import prefix
from rest_framework import urlpatterns
from rest_framework.routers import DefaultRouter
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls,get_ranking, all_ranking_top3
from django.urls import path, include
@ -128,6 +128,8 @@ urlpatterns += [
path('get-goalimage/', views.get_goalimage, name='get-goalimage'),
path('get-photolist/', views.get_photo_list, name='get-photolist'),
path('api/rankings/<str:event_code>/<str:category_name>/', get_ranking, name='get_ranking'),
path('api/rankings/top3/<str:event_code>/', all_ranking_top3, name='all_ranking_top3'),
]

View File

@ -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 # ゴール時間がない場合は失格としない(競技中の可能性)
# durationtimedeltaに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
)