initial setting at 20-Aug-2025
This commit is contained in:
374
rog/views_apis/api_ranking.py
Executable file
374
rog/views_apis/api_ranking.py
Executable file
@ -0,0 +1,374 @@
|
||||
|
||||
# 既存のインポート部分に追加
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rog.models import NewEvent2, Entry
|
||||
import logging
|
||||
from django.db.models import F, Count
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
解説
|
||||
この実装では以下の処理を行っています:
|
||||
|
||||
1.イベントコードとクラス名のパラメータを受け取ります
|
||||
2.パラメータが不足している場合はエラーを返します
|
||||
3.指定されたイベントの存在を確認します
|
||||
4.指定クラスに属する、ゴール済みのチームをスコア降順で取得します
|
||||
5.各チームについて以下の情報を収集します:
|
||||
- 順位(1から始まる)
|
||||
- チーム基本情報(名前、ゼッケン番号)
|
||||
- スコア
|
||||
- レース時間(スタートからゴールまでの時間)
|
||||
- スタート時間・ゴール時間
|
||||
- チェックポイント通過数
|
||||
- オーナー情報(ユーザー名、メールアドレス)
|
||||
6.ランキングの要約情報も提供します:
|
||||
- イベント情報
|
||||
- 指定クラスのチーム総数
|
||||
- ゴール済みチーム数
|
||||
- 未ゴールチーム数(スタート済みだがゴールしていないチーム)
|
||||
|
||||
この実装により、イベント管理者やユーザーはリアルタイムのランキング情報を確認できます。
|
||||
特に結果発表や途中経過の確認に有用です。スコア順にソートされているため、現在の順位が一目でわかります。
|
||||
"""
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_ranking(request):
|
||||
"""
|
||||
指定クラスのランキングを取得
|
||||
|
||||
パラメータ:
|
||||
- class: クラス名
|
||||
- event: イベントコード
|
||||
"""
|
||||
logger.info("get_ranking called")
|
||||
|
||||
# リクエストからパラメータを取得
|
||||
class_name = request.query_params.get('class')
|
||||
event_code = request.query_params.get('event')
|
||||
|
||||
logger.debug(f"Parameters: class={class_name}, event={event_code}")
|
||||
|
||||
# パラメータ検証
|
||||
if not all([class_name, event_code]):
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "クラス名とイベントコードが必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 指定クラスのゴール済みチームを取得(スコア降順)
|
||||
teams = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
goal_info__isnull=False
|
||||
).order_by('-goal_info__score')
|
||||
|
||||
ranking_data = []
|
||||
|
||||
for i, team in enumerate(teams):
|
||||
# チームのスタート情報を取得
|
||||
start_time = None
|
||||
if hasattr(team, 'start_info') and team.start_info:
|
||||
start_time = team.start_info.start_time
|
||||
|
||||
# チームのゴール情報を取得
|
||||
goal_time = None
|
||||
score = 0
|
||||
if hasattr(team, 'goal_info') and team.goal_info:
|
||||
goal_time = team.goal_info.goal_time
|
||||
score = team.goal_info.score or 0
|
||||
|
||||
# レース時間を計算
|
||||
race_time = None
|
||||
if start_time and goal_time:
|
||||
race_seconds = (goal_time - start_time).total_seconds()
|
||||
|
||||
# 時間:分:秒の形式にフォーマット
|
||||
hours, remainder = divmod(int(race_seconds), 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
race_time = f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||
|
||||
# チェックポイント数を取得
|
||||
cp_count = 0
|
||||
try:
|
||||
from rog.models import GpsLog
|
||||
cp_count = GpsLog.objects.filter(entry=team).count()
|
||||
except:
|
||||
pass
|
||||
|
||||
# ランキングデータに追加
|
||||
team_data = {
|
||||
"rank": i + 1, # 1-basedのランキング
|
||||
"team_name": team.team_name,
|
||||
"zekken_number": team.zekken_number,
|
||||
"score": score,
|
||||
"race_time": race_time,
|
||||
"start_time": start_time.strftime("%Y-%m-%d %H:%M:%S") if start_time else None,
|
||||
"goal_time": goal_time.strftime("%Y-%m-%d %H:%M:%S") if goal_time else None,
|
||||
"checkpoint_count": cp_count
|
||||
}
|
||||
|
||||
# オーナー情報があれば追加
|
||||
if hasattr(team, 'owner') and team.owner:
|
||||
team_data["owner_name"] = team.owner.username
|
||||
team_data["owner_email"] = team.owner.email
|
||||
|
||||
ranking_data.append(team_data)
|
||||
|
||||
# 未ゴールチームの数を取得
|
||||
not_finished_count = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
start_info__isnull=False,
|
||||
goal_info__isnull=True
|
||||
).count()
|
||||
|
||||
# 登録チーム総数を取得
|
||||
total_teams = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name
|
||||
).count()
|
||||
|
||||
# イベント情報を取得
|
||||
event_info = {
|
||||
"event_name": event.event_name,
|
||||
"event_description": getattr(event, 'description', None),
|
||||
"event_date": getattr(event, 'event_date', None)
|
||||
}
|
||||
|
||||
if hasattr(event, 'event_date') and event.event_date:
|
||||
event_info["event_date"] = event.event_date.strftime("%Y-%m-%d")
|
||||
|
||||
# 現在の日時
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"event": event_info,
|
||||
"class_name": class_name,
|
||||
"total_teams": total_teams,
|
||||
"finished_teams": len(ranking_data),
|
||||
"not_finished_teams": not_finished_count,
|
||||
"rankings": ranking_data,
|
||||
"timestamp": current_time
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_ranking: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 既存のインポート部分に追加
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rog.models import NewEvent2, Entry
|
||||
import logging
|
||||
from django.db.models import F, Count, Q
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
解説
|
||||
この実装では以下の処理を行っています:
|
||||
|
||||
1.イベントコードのパラメータを受け取ります
|
||||
2.パラメータが不足している場合はエラーを返します
|
||||
3.指定されたイベントの存在を確認します
|
||||
4.イベント内の全クラスを取得します
|
||||
5.各クラスごとに以下の処理を行います:
|
||||
- ゴール済みチームをスコア降順で取得し、上位3件に絞り込みます
|
||||
- 各チームの基本情報(ランク、名前、ゼッケン番号、スコア、レース時間、チェックポイント数)を収集します
|
||||
- クラスの統計情報(総チーム数、ゴール済みチーム数)を計算します
|
||||
- クラスデータとチームデータを階層化して格納します
|
||||
6.イベント全体の統計情報も提供します:
|
||||
- 全クラス数
|
||||
- 全チーム数
|
||||
- ゴール済みチーム数
|
||||
- 未ゴールチーム数(スタート済みだがゴールしていないチーム)
|
||||
この実装により、リアルタイムで全クラスのトップ3ランキングを確認できます。
|
||||
大会のスコアボードやリザルト公開ページなどに利用できます。
|
||||
1つのリクエストで全クラスの情報が取得できるため、複数のクラスを表示するページで効率的に情報を取得できます。
|
||||
"""
|
||||
|
||||
@api_view(['GET'])
|
||||
def all_ranking_top3(request):
|
||||
"""
|
||||
指定イベントの全クラスにおけるトップ3選手のランキングを取得
|
||||
|
||||
パラメータ:
|
||||
- event: イベントコード
|
||||
"""
|
||||
logger.info("all_ranking_top3 called")
|
||||
|
||||
# リクエストからパラメータを取得
|
||||
event_code = request.query_params.get('event')
|
||||
|
||||
logger.debug(f"Parameters: event={event_code}")
|
||||
|
||||
# パラメータ検証
|
||||
if not event_code:
|
||||
logger.warning("Missing required event parameter")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコードが必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# イベント内の全クラスを取得
|
||||
all_classes = Entry.objects.filter(
|
||||
event=event
|
||||
).values_list('class_name', flat=True).distinct()
|
||||
|
||||
class_rankings = []
|
||||
|
||||
for class_name in all_classes:
|
||||
# 指定クラスのゴール済みチームを取得(スコア降順、上位3件)
|
||||
teams = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
goal_info__isnull=False
|
||||
).order_by('-goal_info__score')[:3] # 上位3件
|
||||
|
||||
team_rankings = []
|
||||
|
||||
for i, team in enumerate(teams):
|
||||
# チームのスタート情報を取得
|
||||
start_time = None
|
||||
if hasattr(team, 'start_info') and team.start_info:
|
||||
start_time = team.start_info.start_time
|
||||
|
||||
# チームのゴール情報を取得
|
||||
goal_time = None
|
||||
score = 0
|
||||
if hasattr(team, 'goal_info') and team.goal_info:
|
||||
goal_time = team.goal_info.goal_time
|
||||
score = team.goal_info.score or 0
|
||||
|
||||
# レース時間を計算
|
||||
race_time = None
|
||||
if start_time and goal_time:
|
||||
race_seconds = (goal_time - start_time).total_seconds()
|
||||
|
||||
# 時間:分:秒の形式にフォーマット
|
||||
hours, remainder = divmod(int(race_seconds), 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
race_time = f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||
|
||||
# チェックポイント数を取得
|
||||
cp_count = 0
|
||||
try:
|
||||
from rog.models import GpsLog
|
||||
cp_count = GpsLog.objects.filter(entry=team).count()
|
||||
except:
|
||||
pass
|
||||
|
||||
# チームデータ
|
||||
team_data = {
|
||||
"rank": i + 1, # 1-basedのランキング
|
||||
"team_name": team.team_name,
|
||||
"zekken_number": team.zekken_number,
|
||||
"score": score,
|
||||
"race_time": race_time,
|
||||
"checkpoint_count": cp_count
|
||||
}
|
||||
|
||||
team_rankings.append(team_data)
|
||||
|
||||
# クラスの総チーム数
|
||||
total_teams_in_class = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name
|
||||
).count()
|
||||
|
||||
# ゴール済みチーム数
|
||||
finished_teams_in_class = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
goal_info__isnull=False
|
||||
).count()
|
||||
|
||||
class_data = {
|
||||
"class_name": class_name,
|
||||
"total_teams": total_teams_in_class,
|
||||
"finished_teams": finished_teams_in_class,
|
||||
"top_teams": team_rankings
|
||||
}
|
||||
|
||||
class_rankings.append(class_data)
|
||||
|
||||
# イベント情報を取得
|
||||
event_info = {
|
||||
"event_name": event.event_name,
|
||||
"event_description": getattr(event, 'description', None),
|
||||
"event_date": getattr(event, 'event_date', None)
|
||||
}
|
||||
|
||||
if hasattr(event, 'event_date') and event.event_date:
|
||||
event_info["event_date"] = event.event_date.strftime("%Y-%m-%d")
|
||||
|
||||
# 未ゴールチームの数をイベント全体で取得
|
||||
not_finished_count = Entry.objects.filter(
|
||||
event=event,
|
||||
start_info__isnull=False,
|
||||
goal_info__isnull=True
|
||||
).count()
|
||||
|
||||
# ゴール済みチームの数をイベント全体で取得
|
||||
finished_count = Entry.objects.filter(
|
||||
event=event,
|
||||
goal_info__isnull=False
|
||||
).count()
|
||||
|
||||
# 登録チーム総数をイベント全体で取得
|
||||
total_teams = Entry.objects.filter(
|
||||
event=event
|
||||
).count()
|
||||
|
||||
# 現在の日時
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"event": event_info,
|
||||
"total_classes": len(class_rankings),
|
||||
"total_teams": total_teams,
|
||||
"finished_teams": finished_count,
|
||||
"not_finished_teams": not_finished_count,
|
||||
"rankings_by_class": class_rankings,
|
||||
"timestamp": current_time
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in all_ranking_top3: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user