829 lines
42 KiB
Python
829 lines
42 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
old_rogdb から rogdb への全イベントデータ移行スクリプト(GPS情報移行機能付き)
|
||
FC岐阜の成功事例をベースに全てのイベントのteam/member/entryを移行
|
||
さらに、gifurogeのgps_informationをrogdbのrog_checkinsに移行
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import django
|
||
|
||
if __name__ == '__main__':
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||
django.setup()
|
||
|
||
from django.db import transaction
|
||
from rog.models import NewEvent2, Team, Entry, NewCategory, CustomUser, Member
|
||
import psycopg2
|
||
from collections import defaultdict
|
||
from datetime import datetime, timedelta
|
||
import pytz
|
||
|
||
print("=== old_rogdb から 全イベントデータ移行(GPS情報付き) ===")
|
||
|
||
# GPS情報移行用のヘルパー関数
|
||
def load_event_dates_from_db():
|
||
"""gifurogeのevent_tableからイベントコードと日付のマッピングを取得"""
|
||
event_dates = {}
|
||
try:
|
||
# gifuroge データベースに接続
|
||
conn = psycopg2.connect(
|
||
host='postgres-db',
|
||
database='gifuroge',
|
||
user='admin',
|
||
password='admin123456'
|
||
)
|
||
|
||
cursor = conn.cursor()
|
||
# event_tableからイベントコードと開始日・終了日を取得
|
||
cursor.execute("""
|
||
SELECT event_code, event_day, end_day
|
||
FROM event_table
|
||
WHERE event_code IS NOT NULL AND event_day IS NOT NULL
|
||
ORDER BY event_day
|
||
""")
|
||
|
||
events = cursor.fetchall()
|
||
for event_code, event_day, end_day in events:
|
||
# デバッグ用:読み込まれた生データを表示
|
||
print(f"🔍 生データ: {event_code} | event_day={event_day}({type(event_day)}) | end_day={end_day}({type(end_day)})")
|
||
|
||
# event_dayの日付フォーマットを統一(yyyy-mm-dd形式に変換)
|
||
start_date = None
|
||
end_date = None
|
||
|
||
# event_day(開始日)の処理
|
||
if isinstance(event_day, str):
|
||
if '/' in event_day:
|
||
start_date = normalize_date_format(event_day.replace('/', '-'))
|
||
elif '-' in event_day:
|
||
start_date = normalize_date_format(event_day)
|
||
else:
|
||
date_part = event_day.split(' ')[0] if ' ' in event_day else event_day
|
||
start_date = normalize_date_format(date_part.replace('/', '-'))
|
||
else:
|
||
start_date = normalize_date_format(event_day.strftime('%Y-%m-%d'))
|
||
|
||
# end_day(終了日)の処理
|
||
if end_day:
|
||
if isinstance(end_day, str):
|
||
if '/' in end_day:
|
||
end_date = normalize_date_format(end_day.replace('/', '-'))
|
||
elif '-' in end_day:
|
||
end_date = normalize_date_format(end_day)
|
||
else:
|
||
date_part = end_day.split(' ')[0] if ' ' in end_day else end_day
|
||
end_date = normalize_date_format(date_part.replace('/', '-'))
|
||
else:
|
||
end_date = normalize_date_format(end_day.strftime('%Y-%m-%d'))
|
||
else:
|
||
# end_dayが設定されていない場合は、event_dayと同じ日とする
|
||
end_date = start_date
|
||
|
||
# イベント期間情報を保存
|
||
event_dates[event_code] = {
|
||
'start_date': start_date,
|
||
'end_date': end_date,
|
||
'display_date': start_date # 主要な表示用日付
|
||
}
|
||
|
||
conn.close()
|
||
print(f"📅 event_tableから{len(event_dates)}件のイベント情報を読み込みました:")
|
||
for code, date_info in event_dates.items():
|
||
if date_info['start_date'] == date_info['end_date']:
|
||
print(f" {code}: {date_info['start_date']}")
|
||
else:
|
||
print(f" {code}: {date_info['start_date']} - {date_info['end_date']}")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ event_table読み込みエラー: {e}")
|
||
# フォールバック用のデフォルト値
|
||
event_dates = {
|
||
'gifu2024': {'start_date': '2024-10-27', 'end_date': '2024-10-27', 'display_date': '2024-10-27'},
|
||
'gifu2023': {'start_date': '2023-11-12', 'end_date': '2023-11-12', 'display_date': '2023-11-12'},
|
||
'gifu2022': {'start_date': '2022-11-13', 'end_date': '2022-11-13', 'display_date': '2022-11-13'},
|
||
'test2024': {'start_date': '2024-12-15', 'end_date': '2024-12-15', 'display_date': '2024-12-15'},
|
||
'test2025': {'start_date': '2025-01-25', 'end_date': '2025-01-25', 'display_date': '2025-01-25'},
|
||
'郡上': {'start_date': '2024-06-15', 'end_date': '2024-06-15', 'display_date': '2024-06-15'}
|
||
}
|
||
print(f"デフォルトのイベント日付を使用します: {len(event_dates)}件")
|
||
|
||
return event_dates
|
||
|
||
def get_event_date(event_code, event_dates_cache):
|
||
"""イベントコードから日付を取得(キャッシュ使用)"""
|
||
if event_code in event_dates_cache:
|
||
return event_dates_cache[event_code]['display_date']
|
||
|
||
# 未知のイベントコードの場合、警告を出してデフォルト日付を返す
|
||
print(f"⚠️ 未知のイベントコード '{event_code}' - デフォルト日付2024-01-01を使用")
|
||
return '2024-01-01' # デフォルト日付
|
||
|
||
def normalize_date_format(date_str):
|
||
"""日付文字列をyyyy-mm-dd形式に正規化"""
|
||
try:
|
||
# datetimeオブジェクトの場合
|
||
if hasattr(date_str, 'strftime'):
|
||
return date_str.strftime('%Y-%m-%d')
|
||
|
||
# 文字列の場合
|
||
if isinstance(date_str, str):
|
||
# スラッシュ区切りをハイフン区切りに変換
|
||
if '/' in date_str:
|
||
date_str = date_str.replace('/', '-')
|
||
|
||
# yyyy-m-d や yyyy-mm-d などを yyyy-mm-dd に正規化
|
||
parts = date_str.split('-')
|
||
if len(parts) == 3:
|
||
year, month, day = parts
|
||
return f"{year}-{month.zfill(2)}-{day.zfill(2)}"
|
||
|
||
return date_str
|
||
except:
|
||
return date_str
|
||
|
||
def is_within_event_period(gps_datetime, event_code, event_dates_cache):
|
||
"""GPS記録の日時がイベント期間内かチェック"""
|
||
if event_code not in event_dates_cache:
|
||
return True # 未知のイベントの場合は通す
|
||
|
||
event_info = event_dates_cache[event_code]
|
||
start_date = normalize_date_format(event_info['start_date'])
|
||
end_date = normalize_date_format(event_info['end_date'])
|
||
|
||
try:
|
||
# GPS記録の日付部分を取得して正規化
|
||
gps_date = normalize_date_format(gps_datetime.strftime('%Y-%m-%d'))
|
||
|
||
# イベント期間内かチェック
|
||
return start_date <= gps_date <= end_date
|
||
except Exception as e:
|
||
print(f"日付比較エラー: GPS={gps_datetime}, イベント={event_code}, エラー={e}")
|
||
return True # エラーの場合は通す
|
||
|
||
def parse_goal_time(goal_time_str, event_date):
|
||
"""ゴール時刻をパース"""
|
||
if not goal_time_str or not event_date:
|
||
return None
|
||
|
||
try:
|
||
# HH:MM形式からdatetimeに変換
|
||
time_parts = goal_time_str.split(':')
|
||
if len(time_parts) == 2:
|
||
hour, minute = int(time_parts[0]), int(time_parts[1])
|
||
event_datetime = datetime.strptime(event_date, '%Y-%m-%d')
|
||
goal_datetime = event_datetime.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||
# JST timezone設定
|
||
jst = pytz.timezone('Asia/Tokyo')
|
||
return jst.localize(goal_datetime)
|
||
except Exception as e:
|
||
print(f"ゴール時刻パースエラー: {goal_time_str} - {e}")
|
||
|
||
return None
|
||
|
||
def convert_utc_to_jst(utc_datetime):
|
||
"""UTC時刻をJSTに変換"""
|
||
if not utc_datetime:
|
||
return None
|
||
|
||
try:
|
||
if isinstance(utc_datetime, str):
|
||
utc_datetime = datetime.fromisoformat(utc_datetime.replace('Z', '+00:00'))
|
||
|
||
# UTCとして扱い、JSTに変換
|
||
if utc_datetime.tzinfo is None:
|
||
utc = pytz.UTC
|
||
utc_datetime = utc.localize(utc_datetime)
|
||
|
||
jst = pytz.timezone('Asia/Tokyo')
|
||
return utc_datetime.astimezone(jst)
|
||
except Exception as e:
|
||
print(f"時刻変換エラー: {utc_datetime} - {e}")
|
||
return None
|
||
|
||
def migrate_gps_data():
|
||
"""GPS情報をgifurogeからrogdbに移行"""
|
||
print("\n=== GPS情報移行開始 ===")
|
||
|
||
# まず、イベント日付情報を読み込み
|
||
event_dates_cache = load_event_dates_from_db()
|
||
|
||
try:
|
||
# gifuroge データベースに接続
|
||
gifuroge_conn = psycopg2.connect(
|
||
host='postgres-db',
|
||
database='gifuroge',
|
||
user='admin',
|
||
password='admin123456'
|
||
)
|
||
|
||
# rogdb データベースに接続
|
||
rogdb_conn = psycopg2.connect(
|
||
host='postgres-db',
|
||
database='rogdb',
|
||
user='admin',
|
||
password='admin123456'
|
||
)
|
||
|
||
print("✅ GPS移行用データベース接続成功")
|
||
|
||
with gifuroge_conn.cursor() as source_cursor, rogdb_conn.cursor() as target_cursor:
|
||
|
||
# 既存のGPSチェックイン記録をクリア
|
||
target_cursor.execute("DELETE FROM rog_gpscheckin;")
|
||
print("既存のGPSチェックイン記録をクリアしました")
|
||
|
||
# GPS記録を取得(serial_number < 20000のみ、実際のGPS記録)
|
||
source_cursor.execute("""
|
||
SELECT serial_number, zekken_number, event_code, cp_number, create_at, goal_time
|
||
FROM gps_information
|
||
WHERE serial_number < 20000
|
||
ORDER BY serial_number
|
||
""")
|
||
|
||
gps_records = source_cursor.fetchall()
|
||
print(f"移行対象GPS記録数: {len(gps_records)}件")
|
||
|
||
success_count = 0
|
||
skip_count = 0
|
||
error_count = 0
|
||
event_stats = defaultdict(set)
|
||
skip_stats = defaultdict(int) # スキップ統計
|
||
skip_reasons = defaultdict(int) # スキップ理由別統計
|
||
large_skip_events = set() # 大量スキップイベントの詳細分析用
|
||
skip_date_ranges = defaultdict(list) # スキップされたGPS日付の範囲集計用
|
||
|
||
for record in gps_records:
|
||
serial_number, zekken, event_code, cp_number, create_at, goal_time = record
|
||
|
||
try:
|
||
# イベント日付取得(キャッシュから)
|
||
event_date = get_event_date(event_code, event_dates_cache)
|
||
# event_dateはNoneを返さなくなったので、この条件は不要だが安全のため残す
|
||
if not event_date:
|
||
# 時刻変換してGPS日付を取得
|
||
jst_create_at = convert_utc_to_jst(create_at)
|
||
gps_date = jst_create_at.strftime('%Y-%m-%d') if jst_create_at else 'N/A'
|
||
print(f"⚠️ イベント日付取得失敗: {event_code} GPS日付:{gps_date}")
|
||
skip_count += 1
|
||
skip_stats[event_code] += 1
|
||
skip_reasons["イベント日付取得失敗"] += 1
|
||
continue
|
||
|
||
# 時刻変換
|
||
jst_create_at = convert_utc_to_jst(create_at)
|
||
jst_goal_time = parse_goal_time(goal_time, event_date) if goal_time else None
|
||
|
||
if not jst_create_at:
|
||
print(f"時刻変換失敗: {serial_number}")
|
||
error_count += 1
|
||
skip_stats[event_code] += 1
|
||
skip_reasons["時刻変換失敗"] += 1
|
||
continue
|
||
|
||
# 未知のイベントコードの場合はGPS日付も表示
|
||
if event_code not in event_dates_cache:
|
||
gps_date = jst_create_at.strftime('%Y-%m-%d')
|
||
print(f"⚠️ 未知のイベントコード '{event_code}' GPS日付:{gps_date} - デフォルト日付2024-01-01を使用")
|
||
|
||
# GPS記録がイベント期間内かチェック
|
||
if not is_within_event_period(jst_create_at, event_code, event_dates_cache):
|
||
# GPS日付を正規化(期間外スキップ用)
|
||
gps_date = normalize_date_format(jst_create_at.strftime('%Y-%m-%d'))
|
||
|
||
# 大量スキップイベントの詳細分析
|
||
should_show_detail = (skip_count < 10 or
|
||
(event_code in ['各務原', '岐阜市', '養老ロゲ', '郡上', '大垣2', 'test下呂'] and
|
||
skip_stats[event_code] < 5))
|
||
|
||
if should_show_detail:
|
||
event_info = event_dates_cache.get(event_code, {})
|
||
start_date = normalize_date_format(event_info.get('start_date', 'N/A'))
|
||
end_date = normalize_date_format(event_info.get('end_date', 'N/A'))
|
||
|
||
# 600件超のイベントは特別扱い
|
||
if event_code in ['各務原', '岐阜市', '養老ロゲ', '郡上', '大垣2', 'test下呂']:
|
||
large_skip_events.add(event_code)
|
||
print(f"🔍 大量スキップイベント詳細分析 - {event_code}:")
|
||
print(f" イベントコード: {event_code}")
|
||
print(f" GPS元時刻: {create_at}")
|
||
print(f" GPS JST時刻: {jst_create_at}")
|
||
print(f" GPS日付(正規化前): {jst_create_at.strftime('%Y-%m-%d')}")
|
||
print(f" GPS日付(正規化後): {gps_date}")
|
||
print(f" イベント開始日(正規化前): {event_info.get('start_date', 'N/A')}")
|
||
print(f" イベント開始日(正規化後): {start_date}")
|
||
print(f" イベント終了日(正規化前): {event_info.get('end_date', 'N/A')}")
|
||
print(f" イベント終了日(正規化後): {end_date}")
|
||
print(f" 比較結果: {start_date} <= {gps_date} <= {end_date}")
|
||
print(f" 文字列比較1: '{start_date}' <= '{gps_date}' = {start_date <= gps_date}")
|
||
print(f" 文字列比較2: '{gps_date}' <= '{end_date}' = {gps_date <= end_date}")
|
||
print(f" 年差: GPS年={gps_date[:4]}, イベント年={start_date[:4]}")
|
||
else:
|
||
# デバッグ情報を追加
|
||
print(f"🔍 デバッグ情報:")
|
||
print(f" イベントコード: {event_code}")
|
||
print(f" GPS元時刻: {create_at}")
|
||
print(f" GPS JST時刻: {jst_create_at}")
|
||
print(f" GPS日付(正規化前): {jst_create_at.strftime('%Y-%m-%d')}")
|
||
print(f" GPS日付(正規化後): {gps_date}")
|
||
print(f" イベント開始日(正規化前): {event_info.get('start_date', 'N/A')}")
|
||
print(f" イベント開始日(正規化後): {start_date}")
|
||
print(f" イベント終了日(正規化前): {event_info.get('end_date', 'N/A')}")
|
||
print(f" イベント終了日(正規化後): {end_date}")
|
||
print(f" 比較結果: {start_date} <= {gps_date} <= {end_date}")
|
||
print(f" 文字列比較1: '{start_date}' <= '{gps_date}' = {start_date <= gps_date}")
|
||
print(f" 文字列比較2: '{gps_date}' <= '{end_date}' = {gps_date <= end_date}")
|
||
|
||
print(f"期間外GPS記録スキップ: {event_code} GPS日付:{gps_date} イベント期間:{start_date}-{end_date}")
|
||
|
||
# 大量スキップイベントのGPS日付を記録
|
||
if event_code in ['各務原', '岐阜市', '養老ロゲ', '郡上', '大垣2', 'test下呂']:
|
||
skip_date_ranges[event_code].append(gps_date)
|
||
|
||
skip_count += 1
|
||
skip_stats[event_code] += 1
|
||
skip_reasons["期間外"] += 1
|
||
continue
|
||
|
||
# チェックイン記録挿入
|
||
target_cursor.execute("""
|
||
INSERT INTO rog_gpscheckin (
|
||
zekken, event_code, cp_number, checkin_time, record_time, serial_number
|
||
) VALUES (%s, %s, %s, %s, %s, %s)
|
||
""", (zekken, event_code, cp_number, jst_create_at, jst_create_at, str(serial_number)))
|
||
|
||
event_stats[event_code].add(zekken)
|
||
success_count += 1
|
||
|
||
if success_count % 100 == 0:
|
||
print(f"GPS移行進捗: {success_count}件完了")
|
||
|
||
except Exception as e:
|
||
print(f"GPS移行エラー (Serial: {serial_number}): {e}")
|
||
error_count += 1
|
||
skip_stats[event_code] += 1
|
||
skip_reasons["その他エラー"] += 1
|
||
|
||
# コミット
|
||
rogdb_conn.commit()
|
||
|
||
print(f"\n✅ GPS移行完了:")
|
||
print(f" 成功: {success_count}件")
|
||
print(f" スキップ: {skip_count}件")
|
||
print(f" エラー: {error_count}件")
|
||
|
||
# イベント別統計を表示
|
||
print("\n=== イベント別GPS統計 ===")
|
||
for event_code, zekken_set in event_stats.items():
|
||
print(f" {event_code}: {len(zekken_set)}チーム")
|
||
|
||
# スキップ統計を表示
|
||
print("\n=== スキップ統計(イベント別) ===")
|
||
for event_code, skip_count_by_event in skip_stats.items():
|
||
print(f" {event_code}: {skip_count_by_event}件スキップ")
|
||
|
||
# スキップ理由別統計を表示
|
||
print("\n=== スキップ理由別統計 ===")
|
||
for reason, count in skip_reasons.items():
|
||
print(f" {reason}: {count}件")
|
||
|
||
# 大量スキップイベントの詳細分析結果
|
||
if large_skip_events:
|
||
print("\n=== 600件超大量スキップイベント分析結果 ===")
|
||
for event_code in large_skip_events:
|
||
total_skipped = skip_stats[event_code]
|
||
event_info = event_dates_cache.get(event_code, {})
|
||
|
||
# スキップされたGPS日付の範囲を分析
|
||
skipped_dates = skip_date_ranges.get(event_code, [])
|
||
if skipped_dates:
|
||
# 日付を昇順にソートしてユニーク化
|
||
unique_dates = sorted(set(skipped_dates))
|
||
date_range_start = unique_dates[0] if unique_dates else 'N/A'
|
||
date_range_end = unique_dates[-1] if unique_dates else 'N/A'
|
||
|
||
# 年月日の分析
|
||
year_counts = defaultdict(int)
|
||
month_counts = defaultdict(int)
|
||
for date_str in unique_dates:
|
||
try:
|
||
year = date_str[:4]
|
||
month = date_str[:7] # YYYY-MM
|
||
year_counts[year] += 1
|
||
month_counts[month] += 1
|
||
except:
|
||
pass
|
||
|
||
print(f"📊 {event_code}:")
|
||
print(f" 総スキップ数: {total_skipped}件")
|
||
print(f" 設定イベント期間: {event_info.get('start_date', 'N/A')} - {event_info.get('end_date', 'N/A')}")
|
||
|
||
if skipped_dates:
|
||
print(f" スキップされたGPS記録の期間: {date_range_start} ~ {date_range_end}")
|
||
print(f" ユニークな日付数: {len(unique_dates)}日")
|
||
|
||
# 年別集計
|
||
if year_counts:
|
||
print(f" 年別GPS記録数:")
|
||
for year in sorted(year_counts.keys()):
|
||
print(f" {year}年: {year_counts[year]}日分の記録")
|
||
|
||
# 月別集計(上位5件)
|
||
if month_counts:
|
||
top_months = sorted(month_counts.items(), key=lambda x: x[1], reverse=True)[:5]
|
||
print(f" 月別GPS記録数(上位5件):")
|
||
for month, count in top_months:
|
||
print(f" {month}: {count}日分の記録")
|
||
|
||
print(f" 推測される問題: イベント期間設定が実際のGPS記録日付と大幅にずれている")
|
||
print(f" 解決策: event_tableのevent_day/end_dayを実際のイベント開催日に修正する必要があります")
|
||
print()
|
||
|
||
# 最終統計
|
||
target_cursor.execute("SELECT COUNT(*) FROM rog_gpscheckin")
|
||
total_gps_records = target_cursor.fetchone()[0]
|
||
print(f"\n最終GPS記録数: {total_gps_records}件")
|
||
|
||
gifuroge_conn.close()
|
||
rogdb_conn.close()
|
||
|
||
return success_count > 0
|
||
|
||
except Exception as e:
|
||
print(f"❌ GPS移行エラー: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
try:
|
||
# old_rogdbに直接接続
|
||
old_conn = psycopg2.connect(
|
||
host='postgres-db',
|
||
database='old_rogdb',
|
||
user='admin',
|
||
password='admin123456'
|
||
)
|
||
|
||
print("✅ old_rogdbに接続成功")
|
||
|
||
with old_conn.cursor() as old_cursor:
|
||
# === STEP 0: 移行対象イベントの確認 ===
|
||
print("\n=== STEP 0: 移行対象イベントの確認 ===")
|
||
|
||
# 新DBのイベント一覧を取得
|
||
existing_events = list(NewEvent2.objects.values_list('id', 'event_name'))
|
||
existing_event_ids = [event_id for event_id, _ in existing_events]
|
||
|
||
print(f"新DB既存イベント: {len(existing_events)}件")
|
||
for event_id, event_name in existing_events[:10]:
|
||
print(f" Event {event_id}: {event_name}")
|
||
|
||
# old_rogdbでエントリーがあるイベントを確認
|
||
old_cursor.execute("""
|
||
SELECT e.id, e.event_name, COUNT(re.id) as entry_count
|
||
FROM rog_newevent2 e
|
||
LEFT JOIN rog_entry re ON e.id = re.event_id
|
||
WHERE e.id IN ({})
|
||
GROUP BY e.id, e.event_name
|
||
HAVING COUNT(re.id) > 0
|
||
ORDER BY COUNT(re.id) DESC;
|
||
""".format(','.join(map(str, existing_event_ids))))
|
||
|
||
events_with_entries = old_cursor.fetchall()
|
||
print(f"\n移行対象イベント(エントリーあり): {len(events_with_entries)}件")
|
||
for event_id, event_name, entry_count in events_with_entries:
|
||
print(f" Event {event_id}: '{event_name}' - {entry_count}件のエントリー")
|
||
|
||
# === STEP 1: 全イベントのTeam & Member データ取得 ===
|
||
print("\n=== STEP 1: 全イベントの Team & Member データ取得 ===")
|
||
|
||
# 全イベントのチーム情報を取得
|
||
old_cursor.execute("""
|
||
SELECT DISTINCT rt.id, rt.team_name, rt.owner_id, rt.category_id,
|
||
rc.category_name, cu.email, cu.firstname, cu.lastname, re.event_id
|
||
FROM rog_entry re
|
||
JOIN rog_team rt ON re.team_id = rt.id
|
||
LEFT JOIN rog_newcategory rc ON rt.category_id = rc.id
|
||
LEFT JOIN rog_customuser cu ON rt.owner_id = cu.id
|
||
WHERE re.event_id IN ({})
|
||
ORDER BY re.event_id, rt.id;
|
||
""".format(','.join(map(str, existing_event_ids))))
|
||
|
||
all_team_data = old_cursor.fetchall()
|
||
print(f"全イベント関連チーム: {len(all_team_data)}件")
|
||
|
||
# イベント別チーム数統計
|
||
teams_by_event = defaultdict(int)
|
||
for _, _, _, _, _, _, _, _, event_id in all_team_data:
|
||
teams_by_event[event_id] += 1
|
||
|
||
print("\nイベント別チーム数:")
|
||
for event_id, count in sorted(teams_by_event.items()):
|
||
event_name = next((name for eid, name in existing_events if eid == event_id), "不明")
|
||
print(f" Event {event_id} ({event_name}): {count}チーム")
|
||
|
||
# 全イベントのメンバー情報を取得
|
||
old_cursor.execute("""
|
||
SELECT rm.team_id, rm.user_id, cu.email, cu.firstname, cu.lastname, re.event_id
|
||
FROM rog_entry re
|
||
JOIN rog_member rm ON re.team_id = rm.team_id
|
||
JOIN rog_customuser cu ON rm.user_id = cu.id
|
||
WHERE re.event_id IN ({})
|
||
ORDER BY re.event_id, rm.team_id, rm.user_id;
|
||
""".format(','.join(map(str, existing_event_ids))))
|
||
|
||
all_member_data = old_cursor.fetchall()
|
||
print(f"全イベント関連メンバー: {len(all_member_data)}件")
|
||
|
||
# === STEP 2: ユーザー移行 ===
|
||
print("\n=== STEP 2: ユーザー移行 ===")
|
||
|
||
# 関連するすべてのユーザーを取得
|
||
all_user_ids = set()
|
||
for _, _, owner_id, _, _, _, _, _, _ in all_team_data:
|
||
if owner_id:
|
||
all_user_ids.add(owner_id)
|
||
for _, user_id, _, _, _, _ in all_member_data:
|
||
all_user_ids.add(user_id)
|
||
|
||
if all_user_ids:
|
||
# 大量のユーザーIDに対応するため、バッチで処理
|
||
user_batches = [list(all_user_ids)[i:i+100] for i in range(0, len(all_user_ids), 100)]
|
||
all_user_data = []
|
||
|
||
for batch in user_batches:
|
||
old_cursor.execute(f"""
|
||
SELECT id, email, firstname, lastname, date_joined
|
||
FROM rog_customuser
|
||
WHERE id IN ({','.join(map(str, batch))})
|
||
""")
|
||
all_user_data.extend(old_cursor.fetchall())
|
||
|
||
print(f"移行対象ユーザー: {len(all_user_data)}件")
|
||
|
||
migrated_users = 0
|
||
for user_id, email, first_name, last_name, date_joined in all_user_data:
|
||
user, created = CustomUser.objects.get_or_create(
|
||
id=user_id,
|
||
defaults={
|
||
'email': email or f'user{user_id}@example.com',
|
||
'first_name': first_name or '',
|
||
'last_name': last_name or '',
|
||
'username': email or f'user{user_id}',
|
||
'date_joined': date_joined,
|
||
'is_active': True
|
||
}
|
||
)
|
||
if created:
|
||
migrated_users += 1
|
||
if migrated_users <= 10: # 最初の10件のみ表示
|
||
print(f" ユーザー作成: {email} ({first_name} {last_name})")
|
||
|
||
print(f"✅ ユーザー移行完了: {migrated_users}件作成")
|
||
|
||
# === STEP 3: カテゴリ移行 ===
|
||
print("\n=== STEP 3: カテゴリ移行 ===")
|
||
|
||
migrated_categories = 0
|
||
unique_categories = set()
|
||
for _, _, _, cat_id, cat_name, _, _, _, _ in all_team_data:
|
||
if cat_id and cat_name:
|
||
unique_categories.add((cat_id, cat_name))
|
||
|
||
for cat_id, cat_name in unique_categories:
|
||
category, created = NewCategory.objects.get_or_create(
|
||
id=cat_id,
|
||
defaults={
|
||
'category_name': cat_name,
|
||
'category_number': cat_id
|
||
}
|
||
)
|
||
if created:
|
||
migrated_categories += 1
|
||
print(f" カテゴリ作成: {cat_name}")
|
||
|
||
print(f"✅ カテゴリ移行完了: {migrated_categories}件作成")
|
||
|
||
# === STEP 4: イベント別チーム移行 ===
|
||
print("\n=== STEP 4: イベント別チーム移行 ===")
|
||
|
||
total_migrated_teams = 0
|
||
for event_id, event_name in existing_events:
|
||
if event_id not in teams_by_event:
|
||
continue
|
||
|
||
print(f"\n--- Event {event_id}: {event_name} ---")
|
||
event_teams = [data for data in all_team_data if data[8] == event_id]
|
||
event_migrated_teams = 0
|
||
|
||
for team_id, team_name, owner_id, cat_id, cat_name, email, first_name, last_name, _ in event_teams:
|
||
try:
|
||
# カテゴリを取得
|
||
category = NewCategory.objects.get(id=cat_id) if cat_id else None
|
||
|
||
# チームを作成
|
||
team, created = Team.objects.get_or_create(
|
||
id=team_id,
|
||
defaults={
|
||
'team_name': team_name,
|
||
'owner_id': owner_id or 1,
|
||
'category': category,
|
||
'event_id': event_id
|
||
}
|
||
)
|
||
|
||
if created:
|
||
event_migrated_teams += 1
|
||
total_migrated_teams += 1
|
||
if event_migrated_teams <= 3: # イベントごとに最初の3件のみ表示
|
||
print(f" チーム作成: {team_name} (ID: {team_id})")
|
||
|
||
except Exception as e:
|
||
print(f" ❌ チーム作成エラー: {team_name} - {e}")
|
||
|
||
print(f" ✅ {event_name}: {event_migrated_teams}件のチームを移行")
|
||
|
||
print(f"\n✅ 全チーム移行完了: {total_migrated_teams}件作成")
|
||
|
||
# === STEP 5: メンバー移行 ===
|
||
print("\n=== STEP 5: メンバー移行 ===")
|
||
|
||
total_migrated_members = 0
|
||
for event_id, event_name in existing_events:
|
||
if event_id not in teams_by_event:
|
||
continue
|
||
|
||
event_members = [data for data in all_member_data if data[5] == event_id]
|
||
if not event_members:
|
||
continue
|
||
|
||
print(f"\n--- Event {event_id}: {event_name} ---")
|
||
event_migrated_members = 0
|
||
|
||
for team_id, user_id, email, first_name, last_name, _ in event_members:
|
||
try:
|
||
# チームとユーザーを取得
|
||
team = Team.objects.get(id=team_id)
|
||
user = CustomUser.objects.get(id=user_id)
|
||
|
||
# メンバーを作成
|
||
member, created = Member.objects.get_or_create(
|
||
team=team,
|
||
user=user
|
||
)
|
||
|
||
if created:
|
||
event_migrated_members += 1
|
||
total_migrated_members += 1
|
||
if event_migrated_members <= 3: # イベントごとに最初の3件のみ表示
|
||
print(f" メンバー追加: {email} → {team.team_name}")
|
||
|
||
except Team.DoesNotExist:
|
||
print(f" ⚠️ チーム{team_id}が見つかりません")
|
||
except CustomUser.DoesNotExist:
|
||
print(f" ⚠️ ユーザー{user_id}が見つかりません")
|
||
except Exception as e:
|
||
print(f" ❌ メンバー追加エラー: {e}")
|
||
|
||
print(f" ✅ {event_name}: {event_migrated_members}件のメンバーを移行")
|
||
|
||
print(f"\n✅ 全メンバー移行完了: {total_migrated_members}件作成")
|
||
|
||
# === STEP 6: エントリー移行 ===
|
||
print("\n=== STEP 6: エントリー移行 ===")
|
||
|
||
# データベースのis_trialフィールドにデフォルト値を設定
|
||
print("データベーステーブルのis_trialフィールドを修正中...")
|
||
from django.db import connection as django_conn
|
||
with django_conn.cursor() as django_cursor:
|
||
try:
|
||
django_cursor.execute("""
|
||
ALTER TABLE rog_entry
|
||
ALTER COLUMN is_trial SET DEFAULT FALSE;
|
||
""")
|
||
print(" ✅ is_trialフィールドにデフォルト値を設定")
|
||
except Exception as e:
|
||
print(f" ⚠️ is_trial修正エラー: {e}")
|
||
|
||
total_migrated_entries = 0
|
||
for event_id, event_name in existing_events:
|
||
if event_id not in teams_by_event:
|
||
continue
|
||
|
||
print(f"\n--- Event {event_id}: {event_name} ---")
|
||
|
||
# イベント別エントリーデータを取得
|
||
old_cursor.execute("""
|
||
SELECT re.id, re.team_id, re.zekken_number, re.zekken_label,
|
||
rt.team_name, re.category_id, re.date, re.owner_id,
|
||
rc.category_name
|
||
FROM rog_entry re
|
||
JOIN rog_team rt ON re.team_id = rt.id
|
||
LEFT JOIN rog_newcategory rc ON re.category_id = rc.id
|
||
WHERE re.event_id = %s
|
||
ORDER BY re.zekken_number;
|
||
""", [event_id])
|
||
|
||
event_entry_data = old_cursor.fetchall()
|
||
event_migrated_entries = 0
|
||
|
||
for entry_id, team_id, zekken, label, team_name, cat_id, date, owner_id, cat_name in event_entry_data:
|
||
try:
|
||
# チームとカテゴリを取得
|
||
team = Team.objects.get(id=team_id)
|
||
category = NewCategory.objects.get(id=cat_id) if cat_id else None
|
||
event_obj = NewEvent2.objects.get(id=event_id)
|
||
|
||
# 既存のエントリーをチェック
|
||
existing_entry = Entry.objects.filter(team=team, event=event_obj).first()
|
||
if existing_entry:
|
||
continue
|
||
|
||
# SQLで直接エントリーを挿入
|
||
with django_conn.cursor() as django_cursor:
|
||
django_cursor.execute("""
|
||
INSERT INTO rog_entry
|
||
(date, category_id, event_id, owner_id, team_id, is_active,
|
||
zekken_number, "hasGoaled", "hasParticipated", zekken_label,
|
||
is_trial, staff_privileges, can_access_private_events, team_validation_status)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
|
||
""", [
|
||
event_obj.start_datetime, # date
|
||
cat_id, # category_id
|
||
event_id, # event_id
|
||
owner_id or 1, # owner_id
|
||
team_id, # team_id
|
||
True, # is_active
|
||
int(zekken) if zekken else 0, # zekken_number
|
||
False, # hasGoaled
|
||
False, # hasParticipated
|
||
label or f"{event_name}-{zekken}", # zekken_label
|
||
False, # is_trial
|
||
False, # staff_privileges
|
||
False, # can_access_private_events
|
||
'approved' # team_validation_status
|
||
])
|
||
|
||
event_migrated_entries += 1
|
||
total_migrated_entries += 1
|
||
if event_migrated_entries <= 3: # イベントごとに最初の3件のみ表示
|
||
print(f" エントリー作成: {team_name} - ゼッケン{zekken}")
|
||
|
||
except Team.DoesNotExist:
|
||
print(f" ❌ チーム{team_id}が見つかりません: {team_name}")
|
||
except NewEvent2.DoesNotExist:
|
||
print(f" ❌ イベント{event_id}が見つかりません")
|
||
except Exception as e:
|
||
print(f" ❌ エントリー作成エラー: {team_name} - {e}")
|
||
|
||
print(f" ✅ {event_name}: {event_migrated_entries}件のエントリーを移行")
|
||
|
||
print(f"\n✅ 全エントリー移行完了: {total_migrated_entries}件作成")
|
||
|
||
old_conn.close()
|
||
|
||
# === STEP 7: GPS情報移行 ===
|
||
print("\n=== STEP 7: GPS情報移行 ===")
|
||
|
||
gps_migration_success = migrate_gps_data()
|
||
|
||
if gps_migration_success:
|
||
print("✅ GPS情報移行が正常に完了しました")
|
||
else:
|
||
print("⚠️ GPS情報移行中にエラーが発生しました")
|
||
|
||
# === 最終確認 ===
|
||
print("\n=== 移行結果確認 ===")
|
||
|
||
total_teams = Team.objects.count()
|
||
total_members = Member.objects.count()
|
||
total_entries = Entry.objects.count()
|
||
|
||
print(f"総チーム数: {total_teams}件")
|
||
print(f"総メンバー数: {total_members}件")
|
||
print(f"総エントリー数: {total_entries}件")
|
||
|
||
# GPS記録数も追加で確認
|
||
from django.db import connection as django_conn
|
||
with django_conn.cursor() as cursor:
|
||
cursor.execute("SELECT COUNT(*) FROM rog_gpscheckin")
|
||
gps_count = cursor.fetchone()[0]
|
||
print(f"総GPS記録数: {gps_count}件")
|
||
|
||
# イベント別エントリー統計
|
||
print("\n=== イベント別エントリー統計 ===")
|
||
for event_id, event_name in existing_events[:10]: # 最初の10件を表示
|
||
entry_count = Entry.objects.filter(event_id=event_id).count()
|
||
if entry_count > 0:
|
||
print(f" {event_name}: {entry_count}件")
|
||
|
||
print("\n🎉 全イベントデータ移行(GPS情報付き)が完了しました!")
|
||
print("🎯 通過審査管理画面で全てのイベントのゼッケン番号が表示されるようになります。")
|
||
print("📍 GPS情報も移行され、チェックイン記録が利用可能になります。")
|
||
|
||
except Exception as e:
|
||
print(f"❌ エラーが発生しました: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|