almost finish migrate new circumstances

This commit is contained in:
2025-08-24 19:44:36 +09:00
parent 1ba305641e
commit fe5a044c82
67 changed files with 1194889 additions and 467 deletions

View File

@ -0,0 +1,354 @@
import logging
from django.core.management.base import BaseCommand
from django.db import connections, transaction, connection
from django.db.models import Q
from django.contrib.gis.geos import Point
from django.utils import timezone
from rog.models import Team, NewEvent2, Checkpoint, GpsCheckin, GpsLog, Entry
from datetime import datetime
import psycopg2
from psycopg2.extras import execute_values
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = 'MobServerデータベースからDjangoモデルにデータを移行します'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
dest='dry_run',
help='実際の移行を行わず、処理内容のみを表示します',
)
parser.add_argument(
'--batch-size',
type=int,
default=100,
help='バッチサイズ(デフォルト: 100'
)
def handle(self, *args, **options):
dry_run = options['dry_run']
batch_size = options['batch_size']
if dry_run:
self.stdout.write(self.style.WARNING('ドライランモードで実行中...'))
# MobServerデータベース接続を取得
mobserver_conn = connections['mobserver']
try:
with transaction.atomic():
self.migrate_events(mobserver_conn, dry_run, batch_size)
self.migrate_teams(mobserver_conn, dry_run, batch_size)
self.migrate_checkpoints(mobserver_conn, dry_run, batch_size)
self.migrate_gps_logs(mobserver_conn, dry_run, batch_size)
if dry_run:
raise transaction.TransactionManagementError("ドライランのためロールバックします")
except transaction.TransactionManagementError:
if dry_run:
self.stdout.write(self.style.SUCCESS('ドライランが完了しました(変更は保存されていません)'))
else:
raise
except Exception as e:
logger.error(f'データ移行エラー: {e}')
self.stdout.write(self.style.ERROR(f'エラーが発生しました: {e}'))
raise
def migrate_events(self, conn, dry_run, batch_size):
"""イベント情報を移行"""
self.stdout.write('イベント情報を移行中...')
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM event_table")
rows = cursor.fetchall()
events_migrated = 0
batch_data = []
for row in rows:
(event_code, event_name, start_time, event_day) = row
# start_timeのデータクリーニング
cleaned_start_time = start_time
if start_time and isinstance(start_time, str):
# セミコロンをコロンに置換
cleaned_start_time = start_time.replace(';', ':')
# タイムゾーン情報を含む場合は時間部分のみ抽出
if '+' in cleaned_start_time or 'T' in cleaned_start_time:
try:
from datetime import datetime
dt = datetime.fromisoformat(cleaned_start_time.replace('Z', '+00:00'))
cleaned_start_time = dt.strftime('%H:%M:%S')
except:
cleaned_start_time = None
if not dry_run:
batch_data.append(NewEvent2(
event_code=event_code,
event_name=event_name,
event_day=event_day,
start_time=cleaned_start_time,
))
events_migrated += 1
# バッチ処理
if len(batch_data) >= batch_size:
if not dry_run:
NewEvent2.objects.bulk_create(batch_data, ignore_conflicts=True)
batch_data = []
# 残りのデータを処理
if batch_data and not dry_run:
NewEvent2.objects.bulk_create(batch_data, ignore_conflicts=True)
self.stdout.write(f'{events_migrated}件のイベントを移行しました')
def migrate_teams(self, conn, dry_run, batch_size):
"""チーム情報を移行"""
self.stdout.write('チーム情報を移行中...')
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM team_table")
rows = cursor.fetchall()
teams_migrated = 0
batch_data = []
for row in rows:
(zekken_number, event_code, team_name, class_name, password, trial) = row
# 対応するイベントを取得
try:
event = NewEvent2.objects.get(event_code=event_code)
except NewEvent2.DoesNotExist:
self.stdout.write(self.style.WARNING(f' 警告: イベント {event_code} が見つかりません。スキップします。'))
continue
if not dry_run:
batch_data.append(Team(
zekken_number=zekken_number,
team_name=team_name,
event=event,
class_name=class_name,
password=password,
trial=trial,
))
teams_migrated += 1
# バッチ処理
if len(batch_data) >= batch_size:
if not dry_run:
Team.objects.bulk_create(batch_data, ignore_conflicts=True)
batch_data = []
# 残りのデータを処理
if batch_data and not dry_run:
Team.objects.bulk_create(batch_data, ignore_conflicts=True)
self.stdout.write(f'{teams_migrated}件のチームを移行しました')
def migrate_checkpoints(self, conn, dry_run, batch_size):
"""チェックポイント情報を移行"""
self.stdout.write('チェックポイント情報を移行中...')
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM checkpoint_table")
rows = cursor.fetchall()
checkpoints_migrated = 0
batch_data = []
for row in rows:
(cp_number, event_code, cp_name, latitude, longitude,
photo_point, buy_point, sample_photo, colabo_company_memo) = row
# 対応するイベントを取得
try:
event = NewEvent2.objects.get(event_code=event_code)
except NewEvent2.DoesNotExist:
continue
# 位置情報の処理
location = None
if latitude is not None and longitude is not None:
try:
location = Point(longitude, latitude) # Pointは(longitude, latitude)の順序
except (ValueError, AttributeError):
pass
if not dry_run:
batch_data.append(Checkpoint(
cp_number=cp_number,
event=event,
cp_name=cp_name,
location=location,
photo_point=photo_point or 0,
buy_point=buy_point or 0,
sample_photo=sample_photo,
colabo_company_memo=colabo_company_memo,
))
checkpoints_migrated += 1
# バッチ処理
if len(batch_data) >= batch_size:
if not dry_run:
Checkpoint.objects.bulk_create(batch_data, ignore_conflicts=True)
batch_data = []
# 残りのデータを処理
if batch_data and not dry_run:
Checkpoint.objects.bulk_create(batch_data, ignore_conflicts=True)
self.stdout.write(f'{checkpoints_migrated}件のチェックポイントを移行しました')
def migrate_gps_logs(self, conn, dry_run, batch_size):
"""GPS位置情報を移行"""
print('GPS位置情報を移行中...')
# チームとイベントのマッピングを作成
team_to_event_map = {}
for team in Team.objects.select_related('event'):
if team.event: # eventがNoneでないことを確認
team_to_event_map[team.zekken_number] = team.event.id
# チェックポイントのマッピングを作成
checkpoint_id_map = {}
for checkpoint in Checkpoint.objects.select_related('event'):
if checkpoint.event: # eventがNoneでないことを確認
key = (checkpoint.event.event_code, checkpoint.cp_number)
checkpoint_id_map[key] = checkpoint.id
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM gps_information")
rows = cursor.fetchall()
logs_migrated = 0
batch_data = []
for row in rows:
(serial_number, zekken_number, event_code, cp_number,
image_address, goal_time, late_point, create_at, create_user,
update_at, update_user, buy_flag, minus_photo_flag, colabo_company_memo) = row
# 対応するチームを取得
try:
event = NewEvent2.objects.get(event_code=event_code)
team = Team.objects.get(zekken_number=zekken_number, event=event)
# teamが存在し、eventも存在することを確認
if not team or not team.event:
continue
except (NewEvent2.DoesNotExist, Team.DoesNotExist):
continue
# 対応するチェックポイントを取得(存在する場合)
checkpoint = None
if cp_number is not None and cp_number != -1:
try:
checkpoint = Checkpoint.objects.get(cp_number=cp_number, event=event)
except Checkpoint.DoesNotExist:
pass
# checkin_timeの設定必須フィールド
checkin_time = timezone.now() # デフォルト値
if goal_time:
try:
# goal_timeはHH:MM形式と仮定
from datetime import datetime, time
parsed_time = datetime.strptime(goal_time, '%H:%M').time()
if create_at:
checkin_time = timezone.make_aware(datetime.combine(create_at.date(), parsed_time))
else:
checkin_time = timezone.make_aware(datetime.combine(datetime.now().date(), parsed_time))
except:
checkin_time = timezone.make_aware(create_at) if create_at else timezone.now()
elif create_at:
checkin_time = timezone.make_aware(create_at) if timezone.is_naive(create_at) else create_at
if not dry_run:
# GpsCheckinテーブル用のデータ
batch_data.append({
'event_code': event_code,
'zekken': zekken_number,
'serial_number': serial_number,
'cp_number': cp_number or 0,
'lat': None, # 実際のMobServerデータベースから取得
'lng': None, # 実際のMobServerデータベースから取得
'checkin_time': checkin_time,
'record_time': timezone.make_aware(create_at) if create_at and timezone.is_naive(create_at) else (create_at or timezone.now()),
'location': "", # PostGISポイントは後で設定
'mobserver_id': serial_number,
'event_id': team_to_event_map.get(zekken_number),
'team_id': team.id,
'checkpoint_id': checkpoint.id if checkpoint else None
})
logs_migrated += 1
# バッチ処理
if len(batch_data) >= batch_size:
if not dry_run:
self.bulk_insert_gps_logs(batch_data)
batch_data = []
# 残りのデータを処理
if batch_data and not dry_run:
self.bulk_insert_gps_logs(batch_data)
print(f'{logs_migrated}件のGPS位置情報を移行しました')
def bulk_insert_gps_logs(self, batch_data):
"""
GpsCheckinテーブルに直接SQLを使って挿入
"""
if not batch_data:
return
with connection.cursor() as cursor:
# DjangoのGpsCheckinテーブルに挿入
insert_sql = """
INSERT INTO rog_gpscheckin (
event_code, zekken, serial_number, cp_number, lat, lng,
checkin_time, record_time, location, mobserver_id,
event_id, team_id, checkpoint_id
) VALUES %s
ON CONFLICT DO NOTHING
"""
# locationフィールドを除外してバリューを準備
clean_values = []
for data in batch_data:
# lat/lngがある場合はPostGISポイントを作成、ない場合はNULL
if data['lat'] is not None and data['lng'] is not None:
location_point = f"ST_GeomFromText('POINT({data['lng']} {data['lat']})', 4326)"
else:
location_point = None
clean_values.append((
data['event_code'],
data['zekken'],
data['serial_number'],
data['cp_number'],
data['lat'],
data['lng'],
data['checkin_time'],
data['record_time'],
location_point,
data['mobserver_id'],
data['event_id'],
data['team_id'],
data['checkpoint_id']
))
try:
execute_values(cursor, insert_sql, clean_values, template=None, page_size=100)
except Exception as e:
logger.error(f"データ移行エラー: {e}")
raise