永栄コードのマージ開始

This commit is contained in:
hayano
2024-10-27 18:22:01 +00:00
parent b8d7029965
commit 051916f9f6
15 changed files with 2237 additions and 319 deletions

File diff suppressed because it is too large Load Diff

148
rog/migration_scripts.py Normal file
View File

@ -0,0 +1,148 @@
"""
このコードは永栄コードをNoufferコードに統合するための一時変換コードです。
一旦、完全にマイグレーションでき、ランキングや走行履歴が完成したら、不要になります。
"""
import psycopg2
from PIL import Image
import PIL.ExifTags
from datetime import datetime
import os
def get_gps_from_image(image_path):
"""
画像ファイルからGPS情報を抽出する
Returns: (latitude, longitude) または取得できない場合は (None, None)
"""
try:
with Image.open(image_path) as img:
exif = {
PIL.ExifTags.TAGS[k]: v
for k, v in img._getexif().items()
if k in PIL.ExifTags.TAGS
}
if 'GPSInfo' in exif:
gps_info = exif['GPSInfo']
# 緯度の計算
lat = gps_info[2]
lat = lat[0] + lat[1]/60 + lat[2]/3600
if gps_info[1] == 'S':
lat = -lat
# 経度の計算
lon = gps_info[4]
lon = lon[0] + lon[1]/60 + lon[2]/3600
if gps_info[3] == 'W':
lon = -lon
return lat, lon
except Exception as e:
print(f"GPS情報の抽出に失敗: {e}")
return None, None
def migrate_data():
# コンテナ環境用の接続情報
source_db = {
'dbname': 'gifuroge',
'user': 'admin', # 環境に合わせて変更
'password': 'admin123456', # 環境に合わせて変更
'host': 'localhost', # Dockerのサービス名
'port': '5432'
}
target_db = {
'dbname': 'rogdb',
'user': 'admin', # 環境に合わせて変更
'password': 'admin123456', # 環境に合わせて変更
'host': 'localhost', # Dockerのサービス名
'port': '5432'
}
source_conn = None
target_conn = None
source_cur = None
target_cur = None
try:
print("ソースDBへの接続を試みています...")
source_conn = psycopg2.connect(**source_db)
source_cur = source_conn.cursor()
print("ソースDBへの接続が成功しました")
print("ターゲットDBへの接続を試みています...")
target_conn = psycopg2.connect(**target_db)
target_cur = target_conn.cursor()
print("ターゲットDBへの接続が成功しました")
print("データの取得を開始します...")
source_cur.execute("""
SELECT serial_number, zekken_number, event_code, cp_number, image_address,
goal_time, late_point, create_at, create_user,
update_at, update_user, buy_flag, colabo_company_memo
FROM gps_information
""")
rows = source_cur.fetchall()
print(f"取得したレコード数: {len(rows)}")
processed_count = 0
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, colabo_company_memo) = row
latitude, longitude = None, None
if image_address and os.path.exists(image_address):
latitude, longitude = get_gps_from_image(image_address)
target_cur.execute("""
INSERT INTO gps_checkins (
path_order, zekken_number, event_code, cp_number,
lattitude, longitude, image_address,
image_receipt, image_QR, validate_location,
goal_time, late_point, create_at,
create_user, update_at, update_user,
buy_flag, colabo_company_memo, points
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s
)
""", (
serial_number,
zekken_number, event_code, cp_number,
latitude, longitude, image_address,
True, True, True,
goal_time, late_point, create_at,
create_user, update_at, update_user,
buy_flag if buy_flag is not None else False,
colabo_company_memo if colabo_company_memo else '',
0
))
processed_count += 1
if processed_count % 100 == 0:
print(f"処理済みレコード数: {processed_count}")
target_conn.commit()
print(f"移行完了: {processed_count}件のレコードを処理しました")
except Exception as e:
print(f"エラーが発生しました: {e}")
if target_conn:
target_conn.rollback()
finally:
if source_cur:
source_cur.close()
if target_cur:
target_cur.close()
if source_conn:
source_conn.close()
if target_conn:
target_conn.close()
print("すべての接続をクローズしました")
if __name__ == "__main__":
migrate_data()

View File

@ -72,6 +72,31 @@ def remove_bom_inplace(path):
fp.seek(-bom_length, os.SEEK_CUR)
fp.truncate()
class GifurogeRegister(models.Model):
event_code = models.CharField(max_length=100)
time = models.IntegerField(choices=[(3, '3時間'), (5, '5時間')])
owner_name_kana = models.CharField(max_length=100)
owner_name = models.CharField(max_length=100)
email = models.EmailField()
password = models.CharField(max_length=100)
owner_birthday = models.DateField(blank=True,null=True)
owner_sex = models.CharField(max_length=10,blank=True,null=True)
team_name = models.CharField(max_length=100)
department = models.CharField(max_length=100)
members_count = models.IntegerField()
member2 = models.CharField(max_length=100, blank=True, null=True)
birthday2 = models.DateField(blank=True,null=True)
sex2 = models.CharField(max_length=10,blank=True,null=True)
member3 = models.CharField(max_length=100, blank=True, null=True)
birthday3 = models.DateField(blank=True,null=True)
sex3 = models.CharField(max_length=10,blank=True,null=True)
member4 = models.CharField(max_length=100, blank=True, null=True)
birthday4 = models.DateField(blank=True,null=True)
sex4 = models.CharField(max_length=10,blank=True,null=True)
member5 = models.CharField(max_length=100, blank=True, null=True)
birthday5 = models.DateField(blank=True,null=True)
sex5 = models.CharField(max_length=10,blank=True,null=True)
class CustomUserManager(BaseUserManager):
@ -345,7 +370,7 @@ class Member(models.Model):
unique_together = ('team', 'user')
def __str__(self):
return f"{self.team.zekken_number} - {self.user.lastname} {self.user.firstname}"
return f"{self.team.team_name} - {self.user.lastname} {self.user.firstname}"
#
class Category(models.Model):
@ -504,6 +529,121 @@ class CheckinImages(models.Model):
event_code = models.CharField(_("event code"), max_length=255)
cp_number = models.IntegerField(_("CP numner"))
class GpsCheckin(models.Model):
path_order = models.IntegerField(
null=False,
help_text="チェックポイントの順序番号"
)
zekken_number = models.TextField(
null=False,
help_text="ゼッケン番号"
)
event_code = models.TextField(
null=False,
help_text="イベントコード"
)
cp_number = models.IntegerField(
null=True,
blank=True,
help_text="チェックポイント番号"
)
lattitude = models.FloatField(
null=True,
blank=True,
help_text="緯度:写真から取得"
)
longitude = models.FloatField(
null=True,
blank=True,
help_text="経度:写真から取得"
)
image_address = models.TextField(
null=True,
blank=True,
help_text="チェックイン画像のパス"
)
image_receipt = models.TextField(
null=True,
blank=True,
default=False,
help_text="レシート画像のパス"
)
image_qr = models.BooleanField(
default=False,
help_text="QRコードスキャンフラグ"
)
validate_location = models.BooleanField(
default=False,
help_text="位置情報検証フラグ:画像認識で検証した結果"
)
goal_time = models.TextField(
null=True,
blank=True,
help_text="ゴール時刻=ゴール時のみ使用される。画像から時刻を読み取り設定する。"
)
late_point = models.IntegerField(
null=True,
blank=True,
help_text="遅刻ポイント:ゴールの時刻が制限時間を超えた場合、1分につき-50点が加算。"
)
create_at = models.DateTimeField(
null=True,
blank=True,
help_text="作成日時:データの作成日時"
)
create_user = models.TextField(
null=True,
blank=True,
help_text="作成ユーザー"
)
update_at = models.DateTimeField(
null=True,
blank=True,
help_text="更新日時"
)
update_user = models.TextField(
null=True,
blank=True,
help_text="更新ユーザー"
)
buy_flag = models.BooleanField(
default=False,
help_text="購入フラグ協賛店で購入した場合、無条件でTRUEにする。"
)
colabo_company_memo = models.TextField(
null=False,
default='',
help_text="グループコード:複数のイベントで合算する場合に使用する"
)
points = models.IntegerField(
null=True,
blank=True,
help_text="ポイント:このチェックインによる獲得ポイント。通常ポイントと買い物ポイントは分離される。ゴールの場合には減点なども含む。"
)
class Meta:
db_table = 'gps_checkins'
constraints = [
models.UniqueConstraint(
fields=['zekken_number', 'event_code', 'path_order'],
name='unique_gps_checkin'
)
]
indexes = [
models.Index(fields=['zekken_number', 'event_code','path_order'], name='idx_zekken_event'),
models.Index(fields=['create_at'], name='idx_create_at'),
]
def __str__(self):
return f"{self.event_code}-{self.zekken_number}-{self.path_order}"
def save(self, *args, **kwargs):
# 作成時・更新時のタイムスタンプを自動設定
from django.utils import timezone
if not self.create_at:
self.create_at = timezone.now()
self.update_at = timezone.now()
super().save(*args, **kwargs)
class RogUser(models.Model):
user=models.OneToOneField(CustomUser, on_delete=models.CASCADE)

View File

@ -0,0 +1,11 @@
{% extends "admin/change_list.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
{% blocktranslate with name=opts.verbose_name %}Upload CSV{% endblocktranslate %}
</a>
</li>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "admin/change_list.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
{% translate "Upload CSV" %}
</a>
</li>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}
{% block content %}
<h1>Upload CSV File</h1>
CSV のフォーマット:
イベントコード,時間(3 or 5),代表者かな,代表者名,メール,パスワード,代表者生年月日、代表者性別,チーム名,部門,メンバー数,メンバー2,誕生日2,性別2,メンバー3,誕生日3,性別3,メンバー4,誕生日4,性別4,メンバー5,誕生日5,性別5<br>
(例)<br>
event_code,time,owner_name_kana,owner_name,email,password,owner_birthday,owner_sex,team_name,department,members_count,member2,birthday2,sex2,member3,birthday3,sex3,member4,birthday4,sex4,member5,birthday5,sex5<br>
FC岐阜,3,みやたあきら,宮田 明,hannivalscipio@gmail.com,Sachiko123,タヌキの宮家,一般,3,宮田幸子,1965-4-4,female,川本勇,1965-1-1,male,,,,,,<br>
<br>
この形式のCSVをアップロードすると、以下を実施する。<br>
1)未登録のユーザーの登録をパスワードとともに生成<br>
2)チームを生成し、メンバーを登録<br>
3)イベントコードで示すイベントにエントリー<br>
<br>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="csv_file" required>
<button type="submit">Upload</button>
</form>
{% endblock %}

134
rog/transfer.py Normal file
View File

@ -0,0 +1,134 @@
import psycopg2
from datetime import datetime
import sys
from typing import Dict, List, Tuple
def get_db_connection(dbname: str) -> psycopg2.extensions.connection:
"""データベース接続を確立する"""
try:
return psycopg2.connect(
dbname=dbname,
user='your_username', # 実際のユーザー名に変更してください
password='your_password', # 実際のパスワードに変更してください
host='localhost' # 実際のホスト名に変更してください
)
except psycopg2.Error as e:
print(f"データベース {dbname} への接続エラー: {e}")
sys.exit(1)
def get_source_data() -> List[Dict]:
"""rogdbからデータを取得する"""
conn = get_db_connection('rogdb')
try:
with conn.cursor() as cur:
cur.execute("""
SELECT DISTINCT ON (rci.user_id, rci.cp_number)
rci.team_name,
rci.event_code,
rci.cp_number,
rci.checkinimage,
rci.checkintime,
rci.user_id,
COALESCE(p.point, 0) as late_point
FROM rog_checkinimages rci
LEFT JOIN point p ON p.user_id = rci.user_id
AND p.event_code = rci.event_code
AND p.cp_number = rci.cp_number
WHERE rci.event_code = 'FC岐阜'
ORDER BY rci.user_id, rci.cp_number, rci.checkintime DESC
""")
columns = [desc[0] for desc in cur.description]
return [dict(zip(columns, row)) for row in cur.fetchall()]
finally:
conn.close()
def get_next_serial_number(cur) -> int:
"""次のserial_numberを取得する"""
cur.execute("SELECT nextval('gps_information_serial_number_seq')")
return cur.fetchone()[0]
def insert_into_target(data: List[Dict]) -> Tuple[int, List[str]]:
"""gifurogeデータベースにデータを挿入する"""
conn = get_db_connection('gifuroge')
inserted_count = 0
errors = []
try:
with conn.cursor() as cur:
for record in data:
try:
serial_number = get_next_serial_number(cur)
cur.execute("""
INSERT INTO gps_information (
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
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (
serial_number,
record['team_name'],
record['event_code'],
record['cp_number'],
record['checkinimage'],
record['checkintime'].strftime('%Y-%m-%d %H:%M:%S'),
record['late_point'],
record['checkintime'],
'system',
record['checkintime'],
'system',
False,
False,
''
))
inserted_count += 1
except psycopg2.Error as e:
errors.append(f"Error inserting record for team {record['team_name']}: {str(e)}")
if inserted_count % 100 == 0:
print(f"Processed {inserted_count} records...")
conn.commit()
except psycopg2.Error as e:
conn.rollback()
errors.append(f"Transaction error: {str(e)}")
finally:
conn.close()
return inserted_count, errors
def main():
print("データ移行を開始します...")
# ソースデータの取得
print("ソースデータを取得中...")
source_data = get_source_data()
print(f"取得したレコード数: {len(source_data)}")
# データの挿入
print("データを移行中...")
inserted_count, errors = insert_into_target(source_data)
# 結果の表示
print("\n=== 移行結果 ===")
print(f"処理したレコード数: {inserted_count}")
print(f"エラー数: {len(errors)}")
if errors:
print("\nエラーログ:")
for error in errors:
print(f"- {error}")
if __name__ == "__main__":
main()