almost finish migrate new circumstances
This commit is contained in:
349
rog/models.py
349
rog/models.py
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user