Files
rogaining_srv/rog/views_apis/api_teams.py
2025-08-20 19:15:19 +09:00

696 lines
28 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 既存のインポート部分に追加
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from rog.models import NewEvent2, Entry, GpsLog
import logging
from django.db.models import F, Q
from django.conf import settings
import os
from urllib.parse import urljoin
logger = logging.getLogger(__name__)
"""
解説
このコードでは、以下の処理を行っています:
1.event_codeパラメータを受け取りますオプショナル
2.event_codeが指定されている場合
- そのイベントに所属するチームのみを抽出します
- イベントが存在しない場合はエラーを返します
3.event_codeが指定されていない場合
- すべてのイベントのすべてのチームを抽出します
- チーム情報を整形してJSONレスポンスとして返します
このコードはDjangoの既存モデルを使用し、
Entryモデルと関連するEventおよびOwner(CustomUser)を使用してチーム情報を取得します。
必要に応じて、実際のモデル構造に合わせてフィールド名などを調整してください。
"""
@api_view(['GET'])
def get_team_list(request):
"""
指定されたイベントのチームリスト、または全イベントのチームリストを取得
パラメータ:
- event_code: イベントコード(省略可)
"""
logger.info("get_team_list called")
event_code = request.query_params.get('event_code')
logger.debug(f"Parameters: event_code={event_code}")
try:
# イベントコードが指定された場合、そのイベントのチームのみを抽出
if event_code:
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)
# 指定されたイベントに所属するチームを取得
entries = Entry.objects.filter(event=event).select_related('owner')
else:
# イベントコードが指定されていない場合は全てのエントリーを取得
entries = Entry.objects.all().select_related('owner', 'event')
# チーム情報をJSON形式に整形
team_list = []
for entry in entries:
try:
team_info = {
"zekken_number": entry.zekken_number,
"team_name": entry.team_name,
"class_name": entry.class_name,
"event_code": entry.event.event_name if entry.event else "",
"owner_name": entry.owner.username if entry.owner else "",
"registration_date": entry.created_at.strftime("%Y-%m-%d %H:%M:%S") if hasattr(entry, 'created_at') else ""
}
team_list.append(team_info)
except Exception as e:
logger.error(f"Error processing entry {entry.id}: {str(e)}")
logger.info(f"Successfully retrieved {len(team_list)} teams")
return Response({"status": "OK", "teams": team_list})
except Exception as e:
logger.error(f"Error in get_team_list: {str(e)}")
return Response({"status": "ERROR", "message": "サーバーエラーが発生しました"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
このコードでは以下の処理を行っています:
1.eventパラメータを受け取り、イベントコードの指定を確認します
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントコードでイベントを検索します
- イベントが存在しない場合はエラーを返します
4.見つかったイベントに関連付けられたすべてのエントリーからゼッケン番号を抽出します
5,ゼッケン番号のリストをJSON形式で返します
このエンドポイントは、指定したイベントに参加している全チームのゼッケン番号を一覧で取得するのに役立ちます。
"""
@api_view(['GET'])
def get_zekken_list(request):
"""
指定されたイベントの全ゼッケン番号を取得
パラメータ:
- event: イベントコード
"""
logger.info("get_zekken_list called")
event_code = request.query_params.get('event')
logger.debug(f"Parameters: event={event_code}")
if not event_code:
logger.warning("Event code not provided")
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)
# 指定されたイベントに所属するエントリーからゼッケン番号を抽出
entries = Entry.objects.filter(event=event)
zekken_list = [entry.zekken_number for entry in entries if entry.zekken_number]
logger.info(f"Successfully retrieved {len(zekken_list)} zekken numbers for event {event_code}")
return Response({"status": "OK", "zekken_list": zekken_list})
except Exception as e:
logger.error(f"Error in get_zekken_list: {str(e)}")
return Response({"status": "ERROR", "message": "サーバーエラーが発生しました"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では次のような処理を行っています:
1.必要なパラメータ(ゼッケン番号、イベントコード、チーム名、クラス名、パスワード)を受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントが存在するか確認します
4.指定されたゼッケン番号が既に使用されていないか確認します
5.以下の処理をトランザクションで実行します:
- 新しいユーザーアカウントを作成しますユーザー名は「チーム名_ゼッケン番号」の形式
- ユーザーのパスワードをハッシュ化して保存します
- 新しいチームエントリーを作成し、ユーザーとイベントに紐付けます
6.成功した場合、登録成功のメッセージとチームIDを返します
これにより、チームの登録を安全かつ一貫性を保って行うことができます。
"""
@api_view(['POST'])
def register_team(request):
"""
新しいチームを登録
パラメータ:
- zekken_number: ゼッケン番号
- event_code: イベントコード
- team_name: チーム名
- class_name: クラス名
- password: パスワード
"""
logger.info("register_team called")
# リクエストからパラメータを取得
zekken_number = request.data.get('zekken_number')
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
class_name = request.data.get('class_name')
password = request.data.get('password')
logger.debug(f"Parameters: zekken_number={zekken_number}, event_code={event_code}, "
f"team_name={team_name}, class_name={class_name}")
# パラメータ検証
if not all([zekken_number, event_code, team_name, class_name, password]):
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)
# ゼッケン番号の重複チェック
existing_entry = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).first()
if existing_entry:
logger.warning(f"Duplicate zekken number: {zekken_number} for event: {event_code}")
return Response({
"status": "ERROR",
"message": "このゼッケン番号は既に使用されています"
}, status=status.HTTP_409_CONFLICT)
# トランザクション開始
with transaction.atomic():
# ユーザー作成(ユーザー名はチーム名+ゼッケン番号とする)
username = f"{team_name}_{zekken_number}"
# 同じユーザー名が存在するか確認
if CustomUser.objects.filter(username=username).exists():
username = f"{team_name}_{zekken_number}_{event_code}"
user = CustomUser.objects.create(
username=username,
password=make_password(password) # パスワードをハッシュ化
)
# チーム登録
entry = Entry.objects.create(
owner=user,
zekken_number=zekken_number,
team_name=team_name,
class_name=class_name,
event=event
)
logger.info(f"Successfully registered team: {team_name} with zekken: {zekken_number} for event: {event_code}")
return Response({
"status": "OK",
"message": "チームが正常に登録されました",
"team_id": entry.id
})
except Exception as e:
logger.error(f"Error in register_team: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.必要なパラメータ(ゼッケン番号、新しいチーム名、イベントコード)を受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントが存在するか確認します
4.指定されたゼッケン番号とイベントの組み合わせでエントリーを検索します
- 該当するエントリーが見つからない場合はエラーを返します
5.エントリーのチーム名を新しい値に更新します
6.成功した場合、元のチーム名と新しいチーム名を含む成功メッセージを返します
これにより、既存のチームの名前を安全に更新することができます。
"""
@api_view(['POST'])
def update_team_name(request):
"""
チーム名を更新
パラメータ:
- zekken_number: ゼッケン番号
- new_team_name: 新しいチーム名
- event_code: イベントコード
"""
logger.info("update_team_name called")
# リクエストからパラメータを取得
zekken_number = request.data.get('zekken_number')
new_team_name = request.data.get('new_team_name')
event_code = request.data.get('event_code')
logger.debug(f"Parameters: zekken_number={zekken_number}, "
f"new_team_name={new_team_name}, event_code={event_code}")
# パラメータ検証
if not all([zekken_number, new_team_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)
# ゼッケン番号に対応するエントリーを検索
entry = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).first()
if not entry:
logger.warning(f"Entry not found for zekken number: {zekken_number} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたゼッケン番号のチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# チーム名を更新
old_team_name = entry.team_name
entry.team_name = new_team_name
entry.save()
logger.info(f"Successfully updated team name from '{old_team_name}' to '{new_team_name}' "
f"for zekken: {zekken_number} in event: {event_code}")
return Response({
"status": "OK",
"message": "チーム名が正常に更新されました",
"old_team_name": old_team_name,
"new_team_name": new_team_name
})
except Exception as e:
logger.error(f"Error in update_team_name: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.必要なパラメータ(ゼッケン番号、イベントコード、新しいクラス名)を受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとゼッケン番号のチームを検索します
4.チームのクラス名を更新します
5.成功した場合、元のクラス名と新しいクラス名を含む成功メッセージを返します
オリジナルの仕様通り、このエンドポイントは GET メソッドを使用していますが、
データを更新する操作なのでRESTfulなAPIデザインでは通常は POST または PUT メソッドを使用するべきです。
"""
@api_view(['GET'])
def team_class_changer(request):
"""
チームのクラスを変更
パラメータ:
- zekken: ゼッケン番号
- event: イベントコード
- new_class: 新しいクラス名
"""
logger.info("team_class_changer called")
# リクエストからパラメータを取得
zekken_number = request.query_params.get('zekken')
event_code = request.query_params.get('event')
new_class = request.query_params.get('new_class')
logger.debug(f"Parameters: zekken={zekken_number}, "
f"event={event_code}, new_class={new_class}")
# パラメータ検証
if not all([zekken_number, event_code, new_class]):
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)
# ゼッケン番号に対応するエントリーを検索
entry = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).first()
if not entry:
logger.warning(f"Entry not found for zekken number: {zekken_number} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたゼッケン番号のチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# クラス名を更新
old_class = entry.class_name
entry.class_name = new_class
entry.save()
logger.info(f"Successfully updated class from '{old_class}' to '{new_class}' "
f"for team: {entry.team_name} (zekken: {zekken_number}) in event: {event_code}")
return Response({
"status": "OK",
"message": "クラスが正常に更新されました",
"team_name": entry.team_name,
"old_class": old_class,
"new_class": new_class
})
except Exception as e:
logger.error(f"Error in team_class_changer: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
管理者用のチーム登録エンドポイントを実装します。
これは先ほど実装した /register_team と類似していますが、GETメソッドを使用し、パラメータ名が異なります。
解説
このエンドポイントは、管理者用のチーム登録機能を提供します。前に実装した /register_team との主な違いは以下の点です:
1.GETメソッドを使用している点
2.パラメータ名が異なる:
- event_code → event
- class_name → class
- zekken_number → zekken
- team_name → team
- password → pass
処理の流れは /register_team と同様で、以下のことを行っています:
- パラメータの検証
- イベントの存在確認
- ゼッケン番号の重複チェック
- 新しいユーザーとチームエントリーの作成
本来は「管理者用」とされているため、管理者権限の確認も必要かもしれませんが、
ここでは簡略のためにアクセス制限は実装していません。
実際の運用では、認証・認可の仕組みを追加することをお勧めします。
"""
@api_view(['GET'])
def team_register(request):
"""
チームを登録(管理者用)
パラメータ:
- event: イベントコード
- class: クラス名
- zekken: ゼッケン番号
- team: チーム名
- pass: パスワード
"""
logger.info("team_register called")
# リクエストからパラメータを取得
event_code = request.query_params.get('event')
class_name = request.query_params.get('class')
zekken_number = request.query_params.get('zekken')
team_name = request.query_params.get('team')
password = request.query_params.get('pass')
logger.debug(f"Parameters: event={event_code}, class={class_name}, "
f"zekken={zekken_number}, team={team_name}")
# パラメータ検証
if not all([event_code, class_name, zekken_number, team_name, password]):
logger.warning("Missing required parameters")
return Response({
"status": "ERROR",
"message": "すべてのパラメータが必要です (event, class, zekken, team, pass)"
}, 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)
# ゼッケン番号の重複チェック
existing_entry = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).first()
if existing_entry:
logger.warning(f"Duplicate zekken number: {zekken_number} for event: {event_code}")
return Response({
"status": "ERROR",
"message": "このゼッケン番号は既に使用されています"
}, status=status.HTTP_409_CONFLICT)
# トランザクション開始
with transaction.atomic():
# ユーザー作成(ユーザー名はチーム名+ゼッケン番号とする)
username = f"{team_name}_{zekken_number}"
# 同じユーザー名が存在するか確認
if CustomUser.objects.filter(username=username).exists():
username = f"{team_name}_{zekken_number}_{event_code}"
user = CustomUser.objects.create(
username=username,
password=make_password(password) # パスワードをハッシュ化
)
# チーム登録
entry = Entry.objects.create(
owner=user,
zekken_number=zekken_number,
team_name=team_name,
class_name=class_name,
event=event
)
logger.info(f"Successfully registered team: {team_name} with zekken: {zekken_number} "
f"for event: {event_code} (class: {class_name})")
return Response({
"status": "OK",
"message": "チームが正常に登録されました",
"team_id": entry.id,
"zekken_number": zekken_number,
"team_name": team_name
})
except Exception as e:
logger.error(f"Error in team_register: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.eventパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントコードでイベントを検索します
- イベントが存在しない場合はエラーを返します
4.そのイベントに関連する全エントリーを取得します
- エントリーが存在しない場合は0を返します
5.各エントリーのゼッケン番号を数値として解釈し、最大値を計算します
- 数値に変換できないゼッケン番号は無視されます
6.最大ゼッケン番号をJSON形式で返します
これにより、イベント主催者は既に使用されているゼッケン番号の最大値を知り、
新しいチーム登録時に適切なゼッケン番号を割り当てることができます。
"""
@api_view(['GET'])
def zekken_max_num(request):
"""
指定イベントで使用されている最大のゼッケン番号を取得
パラメータ:
- event: イベントコード
"""
logger.info("zekken_max_num called")
event_code = request.query_params.get('event')
logger.debug(f"Parameters: event={event_code}")
if not event_code:
logger.warning("Event code not provided")
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)
# イベントに関連するエントリーからゼッケン番号の最大値を取得
entries = Entry.objects.filter(event=event)
if not entries.exists():
# エントリーが存在しない場合は0を返す
max_zekken = 0
else:
# 文字列のゼッケン番号を数値として解釈して最大値を取得
# 数値に変換できない場合は除外する
numeric_entries = []
for entry in entries:
try:
if entry.zekken_number and entry.zekken_number.isdigit():
numeric_entries.append(int(entry.zekken_number))
except (ValueError, TypeError):
pass
max_zekken = max(numeric_entries) if numeric_entries else 0
logger.info(f"Maximum zekken number for event {event_code}: {max_zekken}")
return Response({
"status": "OK",
"max_zekken": max_zekken
})
except Exception as e:
logger.error(f"Error in zekken_max_num: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.ゼッケン番号(zekken)とイベントコード(event)パラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントコードでイベントを検索します
- イベントが存在しない場合はエラーを返します
4.指定されたイベント内で、指定ゼッケン番号が既に使用されているかチェックします
5.重複の有無と適切なメッセージをJSON形式で返します
このエンドポイントは、新しいチーム登録時にゼッケン番号の重複を事前にチェックするのに役立ちます。
"""
@api_view(['GET'])
def zekken_double_check(request):
"""
指定ゼッケン番号が既に使用されているか確認
パラメータ:
- zekken: ゼッケン番号
- event: イベントコード
"""
logger.info("zekken_double_check called")
zekken_number = request.query_params.get('zekken')
event_code = request.query_params.get('event')
logger.debug(f"Parameters: zekken={zekken_number}, event={event_code}")
# パラメータ検証
if not all([zekken_number, 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)
# ゼッケン番号の重複チェック
is_duplicate = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).exists()
logger.info(f"Double check for zekken {zekken_number} in event {event_code}: "
f"{'Duplicate' if is_duplicate else 'Not duplicate'}")
return Response({
"status": "OK",
"is_duplicate": is_duplicate,
"message": "このゼッケン番号は既に使用されています" if is_duplicate else "このゼッケン番号は使用可能です"
})
except Exception as e:
logger.error(f"Error in zekken_double_check: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)