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

@ -309,10 +309,11 @@ class TempUser(models.Model):
return timezone.now() <= self.expires_at
class NewEvent2(models.Model):
# 既存フィールド
event_name = models.CharField(max_length=255, unique=True)
event_description=models.TextField(max_length=255,blank=True, null=True)
start_datetime = models.DateTimeField(default=timezone.now)
end_datetime = models.DateTimeField()
end_datetime = models.DateTimeField(null=True, blank=True)
deadlineDateTime = models.DateTimeField(null=True, blank=True)
#// Added @2024-10-21
@ -325,8 +326,19 @@ class NewEvent2(models.Model):
class_solo_female = models.BooleanField(default=True)
self_rogaining = models.BooleanField(default=False)
# MobServer統合フィールド
event_code = models.CharField(max_length=50, unique=True, blank=True, null=True) # event_table.event_code
start_time = models.CharField(max_length=20, blank=True, null=True) # event_table.start_time
event_day = models.CharField(max_length=20, blank=True, null=True) # event_table.event_day
# 会場情報統合
venue_location = models.PointField(null=True, blank=True, srid=4326)
venue_address = models.CharField(max_length=500, blank=True, null=True)
def __str__(self):
if self.event_code:
return f"{self.event_code} - {self.event_name}"
return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}"
def save(self, *args, **kwargs):
@ -347,16 +359,38 @@ def get_default_category():
class Team(models.Model):
# zekken_number = models.CharField(max_length=255, unique=True)
# 既存フィールド
team_name = models.CharField(max_length=255)
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='owned_teams', blank=True, null=True)
category = models.ForeignKey('NewCategory', on_delete=models.SET_DEFAULT, default=get_default_category)
# MobServer統合フィールド
zekken_number = models.CharField(max_length=20, blank=True, null=True) # team_table.zekken_number
event = models.ForeignKey('NewEvent2', on_delete=models.CASCADE, blank=True, null=True) # team_table.event_code
password = models.CharField(max_length=100, blank=True, null=True) # team_table.password
class_name = models.CharField(max_length=100, blank=True, null=True) # team_table.class_name
trial = models.BooleanField(default=False) # team_table.trial
# 地理情報
location = models.PointField(null=True, blank=True, srid=4326)
# 統合管理フィールド
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
# class Meta:
# unique_together = ('zekken_number', 'category')
class Meta:
constraints = [
models.UniqueConstraint(
fields=['zekken_number', 'event'],
name='unique_team_per_event',
condition=models.Q(zekken_number__isnull=False, event__isnull=False)
)
]
def __str__(self):
return f"{self.team_name}, owner:{self.owner.lastname} {self.owner.firstname}"
if self.zekken_number and self.event:
return f"{self.zekken_number}-{self.team_name} ({self.event.event_name})"
return f"{self.team_name}, owner:{self.owner.lastname if self.owner else 'None'} {self.owner.firstname if self.owner else ''}"
class Member(models.Model):
@ -540,14 +574,53 @@ class CheckinImages(models.Model):
event_code = models.CharField(_("event code"), max_length=255)
cp_number = models.IntegerField(_("CP numner"))
class Checkpoint(models.Model):
"""チェックポイント管理モデルMobServer統合"""
# MobServer完全統合
cp_number = models.IntegerField() # checkpoint_table.cp_number
event = models.ForeignKey('NewEvent2', on_delete=models.CASCADE, blank=True, null=True)
cp_name = models.CharField(max_length=200, blank=True, null=True) # checkpoint_table.cp_name
# 位置情報PostGIS対応
location = models.PointField(srid=4326, blank=True, null=True) # latitude, longitude統合
# ポイント情報
photo_point = models.IntegerField(default=0) # checkpoint_table.photo_point
buy_point = models.IntegerField(default=0) # checkpoint_table.buy_point
# サンプル・メモ
sample_photo = models.CharField(max_length=500, blank=True, null=True)
colabo_company_memo = models.TextField(blank=True, null=True)
# 統合管理フィールド
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['cp_number', 'event'],
name='unique_cp_per_event'
)
]
indexes = [
models.Index(fields=['event', 'cp_number'], name='idx_checkpoint_event_cp'),
GistIndex(fields=['location'], name='idx_checkpoint_location'),
]
def __str__(self):
return f"CP{self.cp_number} - {self.cp_name} ({self.event.event_code if self.event.event_code else self.event.event_name})"
class GpsCheckin(models.Model):
id = models.AutoField(primary_key=True) # 明示的にidフィールドを追加
path_order = models.IntegerField(
null=False,
default=0,
help_text="チェックポイントの順序番号"
)
zekken_number = models.TextField(
null=False,
default='',
help_text="ゼッケン番号"
)
event_id = models.IntegerField(
@ -557,6 +630,7 @@ class GpsCheckin(models.Model):
)
event_code = models.TextField(
null=False,
default='',
help_text="イベントコード"
)
cp_number = models.IntegerField(
@ -637,6 +711,76 @@ class GpsCheckin(models.Model):
blank=True,
help_text="ポイント:このチェックインによる獲得ポイント。通常ポイントと買い物ポイントは分離される。ゴールの場合には減点なども含む。"
)
# MobServer統合フィールド
serial_number = models.IntegerField(
null=True,
blank=True,
help_text="MobServer gps_information.serial_number"
)
team = models.ForeignKey(
'Team',
on_delete=models.CASCADE,
null=True,
blank=True,
help_text="統合チームリレーション"
)
checkpoint = models.ForeignKey(
'Checkpoint',
on_delete=models.CASCADE,
null=True,
blank=True,
help_text="統合チェックポイントリレーション"
)
minus_photo_flag = models.BooleanField(
default=False,
help_text="MobServer gps_information.minus_photo_flag"
)
# 通過審査管理フィールド
validation_status = models.CharField(
max_length=20,
choices=[
('PENDING', '審査待ち'),
('APPROVED', '承認'),
('REJECTED', '却下'),
('AUTO_APPROVED', '自動承認')
],
default='PENDING',
help_text="通過審査ステータス"
)
validation_comment = models.TextField(
null=True,
blank=True,
help_text="審査コメント"
)
validated_at = models.DateTimeField(
null=True,
blank=True,
help_text="審査実施日時"
)
validated_by = models.CharField(
max_length=255,
null=True,
blank=True,
help_text="審査実施者"
)
validation_comment = models.TextField(
null=True,
blank=True,
help_text="審査コメント・理由"
)
validated_by = models.CharField(
max_length=255,
null=True,
blank=True,
help_text="審査者"
)
validated_at = models.DateTimeField(
null=True,
blank=True,
help_text="審査日時"
)
class Meta:
db_table = 'gps_checkins'
@ -800,6 +944,161 @@ class templocation(models.Model):
def __str__(self):
return self.location_name
class Location2025(models.Model):
"""
2025年版チェックポイント管理モデル
CSVアップロード対応の新しいチェックポイント管理システム
"""
# 基本情報
cp_number = models.IntegerField(_('CP番号'), db_index=True)
event = models.ForeignKey('NewEvent2', on_delete=models.CASCADE, verbose_name=_('イベント'))
cp_name = models.CharField(_('CP名'), max_length=255)
# 位置情報
latitude = models.FloatField(_('緯度'), null=True, blank=True)
longitude = models.FloatField(_('経度'), null=True, blank=True)
location = models.PointField(_('位置'), srid=4326, null=True, blank=True)
# ポイント情報
cp_point = models.IntegerField(_('チェックポイント得点'), default=10)
photo_point = models.IntegerField(_('写真ポイント'), default=0)
buy_point = models.IntegerField(_('買い物ポイント'), default=0)
# チェックイン設定
checkin_radius = models.FloatField(_('チェックイン範囲(m)'), default=15.0)
auto_checkin = models.BooleanField(_('自動チェックイン'), default=False)
# 営業情報
shop_closed = models.BooleanField(_('休業中'), default=False)
shop_shutdown = models.BooleanField(_('閉業'), default=False)
opening_hours = models.TextField(_('営業時間'), blank=True, null=True)
# 詳細情報
address = models.CharField(_('住所'), max_length=512, blank=True, null=True)
phone = models.CharField(_('電話番号'), max_length=32, blank=True, null=True)
website = models.URLField(_('ウェブサイト'), blank=True, null=True)
description = models.TextField(_('説明'), blank=True, null=True)
# 管理情報
is_active = models.BooleanField(_('有効'), default=True, db_index=True)
sort_order = models.IntegerField(_('表示順'), default=0)
# CSVアップロード関連
csv_source_file = models.CharField(_('CSVファイル名'), max_length=255, blank=True, null=True)
csv_upload_date = models.DateTimeField(_('CSVアップロード日時'), null=True, blank=True)
csv_upload_user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True,
related_name='location2025_csv_uploads', verbose_name=_('CSVアップロードユーザー'))
# タイムスタンプ
created_at = models.DateTimeField(_('作成日時'), auto_now_add=True)
updated_at = models.DateTimeField(_('更新日時'), auto_now=True)
created_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True,
related_name='location2025_created', verbose_name=_('作成者'))
updated_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True,
related_name='location2025_updated', verbose_name=_('更新者'))
class Meta:
db_table = 'rog_location2025'
verbose_name = _('チェックポイント2025')
verbose_name_plural = _('チェックポイント2025')
unique_together = ['cp_number', 'event']
ordering = ['event', 'sort_order', 'cp_number']
indexes = [
models.Index(fields=['event', 'cp_number'], name='location2025_event_cp_idx'),
models.Index(fields=['event', 'is_active'], name='location2025_event_active_idx'),
models.Index(fields=['csv_upload_date'], name='location2025_csv_date_idx'),
GistIndex(fields=['location'], name='location2025_location_gist_idx'),
]
def save(self, *args, **kwargs):
# 緯度経度からLocationフィールドを自動生成
if self.latitude and self.longitude:
from django.contrib.gis.geos import Point
self.location = Point(self.longitude, self.latitude, srid=4326)
super().save(*args, **kwargs)
def __str__(self):
return f"{self.event.event_name} - CP{self.cp_number}: {self.cp_name}"
@property
def total_point(self):
"""総得点を計算"""
return self.cp_point + self.photo_point + self.buy_point
@classmethod
def import_from_csv(cls, csv_file, event, user=None):
"""
CSVファイルからチェックポイントデータをインポート
CSV形式:
cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phone,description
"""
import csv
import io
from django.utils import timezone
if isinstance(csv_file, str):
# ファイルパスの場合
with open(csv_file, 'r', encoding='utf-8') as f:
csv_content = f.read()
else:
# アップロードされたファイルの場合
csv_content = csv_file.read().decode('utf-8')
csv_reader = csv.DictReader(io.StringIO(csv_content))
created_count = 0
updated_count = 0
errors = []
for row_num, row in enumerate(csv_reader, start=2):
try:
cp_number = int(row.get('cp_number', 0))
if cp_number <= 0:
errors.append(f"{row_num}: CP番号が無効です")
continue
defaults = {
'cp_name': row.get('cp_name', f'CP{cp_number}'),
'latitude': float(row['latitude']) if row.get('latitude') else None,
'longitude': float(row['longitude']) if row.get('longitude') else None,
'cp_point': int(row.get('cp_point', 10)),
'photo_point': int(row.get('photo_point', 0)),
'buy_point': int(row.get('buy_point', 0)),
'address': row.get('address', ''),
'phone': row.get('phone', ''),
'description': row.get('description', ''),
'csv_source_file': getattr(csv_file, 'name', 'uploaded_file.csv'),
'csv_upload_date': timezone.now(),
'csv_upload_user': user,
'updated_by': user,
}
if user:
defaults['created_by'] = user
obj, created = cls.objects.update_or_create(
cp_number=cp_number,
event=event,
defaults=defaults
)
if created:
created_count += 1
else:
updated_count += 1
except (ValueError, KeyError) as e:
errors.append(f"{row_num}: {str(e)}")
continue
return {
'created': created_count,
'updated': updated_count,
'errors': errors
}
class Location_line(models.Model):
location_id=models.IntegerField(_('Location id'), blank=True, null=True)
location_name=models.CharField(_('Location Name'), max_length=255)
@ -1409,6 +1708,39 @@ def publish_data(sender, instance, created, **kwargs):
# 既存のモデルに追加=> 通過記録に相応しい名称に変更すべき
def get_default_entry():
"""
デフォルトのEntryを取得または作成する
"""
try:
# NewEvent2のデフォルトイベントを取得
default_event = NewEvent2.objects.first()
if default_event:
# デフォルトチームを取得
default_team = Team.objects.first()
if default_team:
# 既存のEntryを取得
entry = Entry.objects.filter(
teams=default_team,
event=default_event
).first()
if entry:
return entry.id
# 新しいEntryを作成
from django.contrib.auth import get_user_model
User = get_user_model()
default_user = User.objects.first()
if default_user:
entry = Entry.objects.create(
event=default_event,
main_user=default_user
)
entry.teams.add(default_team)
return entry.id
except:
pass
return None
class GpsLog(models.Model):
"""
@ -1417,12 +1749,9 @@ class GpsLog(models.Model):
"""
serial_number = models.IntegerField(null=False)
# 新規追加
entry = models.ForeignKey(Entry, on_delete=models.CASCADE, related_name='checkpoints')
# Entry へ移行
zekken_number = models.TextField(null=False)
event_code = models.TextField(null=False)
zekken_number = models.TextField(null=False, default='')
event_code = models.TextField(null=False, default='')
cp_number = models.TextField(null=True, blank=True)
image_address = models.TextField(null=True, blank=True)