2161 lines
95 KiB
Python
Executable File
2161 lines
95 KiB
Python
Executable File
from django.contrib.auth.hashers import make_password
|
||
from dataclasses import field
|
||
import email
|
||
from enum import unique
|
||
from pyexpat import model
|
||
from sre_constants import CH_LOCALE
|
||
from typing import ChainMap
|
||
from django.contrib.gis.db import models
|
||
from django.contrib.postgres.fields import ArrayField
|
||
from django.utils import timezone
|
||
from datetime import timedelta
|
||
try:
|
||
from django.db.models import JSONField
|
||
except ImportError:
|
||
from django.contrib.postgres.fields import JSONField
|
||
from django.utils.translation import gettext_lazy as _
|
||
from django.contrib.auth.models import User
|
||
from django.db.models.signals import post_save, post_delete, pre_save
|
||
from django.dispatch import receiver
|
||
import geopandas as gpd
|
||
from sqlalchemy import *
|
||
from geoalchemy2 import Geometry, WKTElement
|
||
import os, zipfile, glob
|
||
import environ
|
||
from geo.Postgres import Db
|
||
from sqlalchemy.sql.functions import mode
|
||
from .mapping import location_mapping, location_line_mapping, location_polygon_mapping
|
||
from .choices import LAYER_CHOICES
|
||
from django.contrib.gis.utils import LayerMapping
|
||
from django.apps import apps
|
||
from django.db import transaction
|
||
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
|
||
from django.contrib.postgres.indexes import GistIndex
|
||
from django.utils import timezone
|
||
from datetime import timedelta,date
|
||
|
||
from django.contrib.gis.geos import Point,MultiPoint
|
||
|
||
#from django.db import models
|
||
from django.core.exceptions import ValidationError
|
||
|
||
import csv
|
||
import codecs
|
||
import sys
|
||
import time
|
||
import uuid
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
env = environ.Env(DEBUG=(bool, False))
|
||
environ.Env.read_env(env_file=".env")
|
||
|
||
db = Db(dbname=env("POSTGRES_DBNAME"), user=env("POSTGRES_USER"), password=env("POSTGRES_PASS"), host=env("PG_HOST"), port=env("PG_PORT"))
|
||
|
||
|
||
def get_file_path(instance, filename):
|
||
ext = filename.split('.')[-1]
|
||
filename = "%s/%s.%s" % (uuid.uuid4(), uuid.uuid4(), ext)
|
||
return os.path.join('uploads/geoms', filename)
|
||
|
||
|
||
def remove_bom_inplace(path):
|
||
"""Removes BOM mark, if it exists, from a file and rewrites it in-place"""
|
||
buffer_size = 4096
|
||
bom_length = len(codecs.BOM_UTF8)
|
||
|
||
with open(path, mode="r+b") as fp:
|
||
chunk = fp.read(buffer_size)
|
||
if chunk.startswith(codecs.BOM_UTF8):
|
||
i = 0
|
||
chunk = chunk[bom_length:]
|
||
while chunk:
|
||
fp.seek(i)
|
||
fp.write(chunk)
|
||
i += len(chunk)
|
||
fp.seek(bom_length, os.SEEK_CUR)
|
||
chunk = fp.read(buffer_size)
|
||
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):
|
||
|
||
def create_user(self, email, password=None, **other_fields):
|
||
if not email:
|
||
raise ValueError(_("You must provide an email address"))
|
||
email = self.normalize_email(email)
|
||
user = self.model(email=email, **other_fields)
|
||
user.set_password(password)
|
||
user.save(using=self._db)
|
||
|
||
return user
|
||
|
||
def create_superuser(self, email, password, group, event_code=None, team_name=None, **other_fields):
|
||
# Providing default values for event_code and team_name if they are not provided
|
||
if event_code is None:
|
||
event_code = 'test' # replace this with some default value
|
||
if team_name is None:
|
||
team_name = 'test' # replace this with some default value
|
||
|
||
other_fields.setdefault('is_staff', True)
|
||
other_fields.setdefault('is_superuser', True)
|
||
other_fields.setdefault('is_active', True)
|
||
|
||
if other_fields.get('is_staff') is not True:
|
||
raise ValueError(_('Superuser must be assigned to staff'))
|
||
if other_fields.get('is_superuser') is not True:
|
||
raise ValueError(_('Superuser must have is_superuser set to True'))
|
||
|
||
return self.create_user(email, password, **other_fields)
|
||
|
||
|
||
|
||
class JpnAdminMainPerf(models.Model):
|
||
geom = models.MultiPolygonField(blank=True, null=True)
|
||
adm0_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
|
||
class Meta:
|
||
managed = False
|
||
db_table = 'jpn_admin_main_perf'
|
||
indexes = [
|
||
models.Index(fields=['geom'], name='jpn_admin_main_perf_geom_idx'),
|
||
# Add other fields for indexing as per the requirements
|
||
]
|
||
|
||
|
||
# class JpnAdminPerf(models.Model):
|
||
# geom = models.MultiLineStringField(blank=True, null=True)
|
||
# et_id = models.IntegerField(blank=True, null=True)
|
||
# et_right = models.CharField(max_length=80, blank=True, null=True)
|
||
# et_left = models.CharField(max_length=80, blank=True, null=True)
|
||
# adm2_l = models.CharField(max_length=50, blank=True, null=True)
|
||
# adm1_l = models.CharField(max_length=50, blank=True, null=True)
|
||
# adm0_l = models.CharField(max_length=50, blank=True, null=True)
|
||
# adm0_r = models.CharField(max_length=50, blank=True, null=True)
|
||
# adm1_r = models.CharField(max_length=50, blank=True, null=True)
|
||
# adm2_r = models.CharField(max_length=50, blank=True, null=True)
|
||
# admlevel = models.IntegerField(blank=True, null=True)
|
||
|
||
# class Meta:
|
||
# managed = False
|
||
# db_table = 'jpn_admin_perf'
|
||
# indexes = [
|
||
# models.Index(fields=['geom'], name='jpn_admin_perf_geom_idx'),
|
||
# # Add other fields for indexing as per the requirements
|
||
# ]
|
||
|
||
# ###
|
||
# ### Cities
|
||
# ###
|
||
class JpnSubPerf(models.Model):
|
||
geom = models.MultiPolygonField(blank=True, null=True)
|
||
adm0_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
name_modified = models.CharField(max_length=254, blank=True, null=True)
|
||
area_name = models.CharField(max_length=254, blank=True, null=True)
|
||
list_order =models.IntegerField(default=0)
|
||
|
||
class Meta:
|
||
managed = False
|
||
db_table = 'jpn_sub_perf'
|
||
indexes = [
|
||
models.Index(fields=['geom'], name='jpn_sub_perf_geom_idx'),
|
||
# Add other fields for indexing as per the requirements
|
||
]
|
||
|
||
###
|
||
### Gifu Areas
|
||
###
|
||
class GifuAreas(models.Model):
|
||
geom = models.MultiPolygonField(blank=True, null=True)
|
||
adm0_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm0_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm1_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_ja = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_en = models.CharField(max_length=254, blank=True, null=True)
|
||
adm2_pcode = models.CharField(max_length=254, blank=True, null=True)
|
||
area_nm = models.CharField(max_length=254, blank=True, null=True)
|
||
|
||
class Meta:
|
||
managed = False
|
||
db_table = 'gifu_areas'
|
||
indexes = [
|
||
models.Index(fields=['geom'], name='gifu_areas_geom_idx'),
|
||
# Add other fields for indexing as per the requirements
|
||
]
|
||
|
||
|
||
class UserUpload(models.Model):
|
||
name = models.CharField(_("User uploads"), max_length=255)
|
||
file = models.FileField(upload_to=get_file_path, blank=True)
|
||
uploaded_date = models.DateField(auto_now_add=True)
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
class UserUploadUser(models.Model):
|
||
userfile=models.CharField(_('User file'), max_length=2048 ,blank=True, null=True)
|
||
email=models.CharField(_('User Email'), max_length=255)
|
||
|
||
|
||
class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||
class Groups(models.TextChoices):
|
||
GB1 = '大垣-初心者', '大垣-初心者'
|
||
GB2 = '大垣-3時間', '大垣-3時間'
|
||
GB3 = '大垣-5時間', '大垣-5時間'
|
||
|
||
email = models.EmailField(unique=True)
|
||
firstname = models.CharField(max_length=255,blank=True, null=True)
|
||
lastname = models.CharField(max_length=255, blank=True, null=True)
|
||
date_of_birth = models.DateField(blank=True, null=True)
|
||
female = models.BooleanField(default=False)
|
||
group = models.CharField(max_length=255,blank=True)
|
||
is_active = models.BooleanField(default=True)
|
||
is_staff = models.BooleanField(default=False)
|
||
date_joined = models.DateTimeField(default=timezone.now)
|
||
|
||
is_rogaining = models.BooleanField(default=False)
|
||
zekken_number = models.CharField(_("Zekken Number"), max_length=255, blank=True, null=True)
|
||
event_code = models.CharField(_("Event Code"), max_length=255, blank=True, null=True)
|
||
team_name = models.CharField(_("Team Name"), max_length=255, blank=True, null=True)
|
||
|
||
|
||
USERNAME_FIELD = 'email'
|
||
REQUIRED_FIELDS = []
|
||
|
||
objects = CustomUserManager()
|
||
|
||
def set_password(self, raw_password):
|
||
self.password = make_password(raw_password)
|
||
self._password = raw_password
|
||
|
||
def __str__(self):
|
||
return self.email
|
||
|
||
class TempUser(models.Model):
|
||
email = models.EmailField(unique=True)
|
||
password = models.CharField(max_length=128)
|
||
is_rogaining = models.BooleanField(default=False)
|
||
zekken_number = models.CharField(max_length=255, blank=True, null=True)
|
||
event_code = models.CharField(max_length=255, blank=True, null=True)
|
||
team_name = models.CharField(max_length=255, blank=True, null=True)
|
||
group = models.CharField(max_length=255)
|
||
firstname = models.CharField(max_length=255,blank=True, null=True)
|
||
lastname = models.CharField(max_length=255, blank=True, null=True)
|
||
date_of_birth = models.DateField(blank=True, null=True)
|
||
female = models.BooleanField(default=False)
|
||
verification_code = models.UUIDField(default=uuid.uuid4, editable=False)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
expires_at = models.DateTimeField()
|
||
|
||
def set_password(self, raw_password):
|
||
self.password = make_password(raw_password)
|
||
|
||
def check_password(self, raw_password):
|
||
return check_password(raw_password, self.password)
|
||
|
||
# TempUserの作成時にこのメソッドを使用
|
||
@classmethod
|
||
def create_temp_user(cls, email, password, **kwargs):
|
||
temp_user = cls(email=email, **kwargs)
|
||
temp_user.set_password(password)
|
||
temp_user.save()
|
||
return temp_user
|
||
|
||
def __str__(self):
|
||
return self.email
|
||
|
||
def save(self, *args, **kwargs):
|
||
if not self.expires_at:
|
||
self.expires_at = timezone.now() + timedelta(hours=24) # 24時間の有効期限
|
||
super().save(*args, **kwargs)
|
||
|
||
def is_valid(self):
|
||
return timezone.now() <= self.expires_at
|
||
|
||
|
||
class AppVersion(models.Model):
|
||
"""アプリバージョン管理モデル"""
|
||
|
||
PLATFORM_CHOICES = [
|
||
('android', 'Android'),
|
||
('ios', 'iOS'),
|
||
]
|
||
|
||
version = models.CharField(max_length=20, help_text="セマンティックバージョン (1.2.3)")
|
||
platform = models.CharField(max_length=10, choices=PLATFORM_CHOICES)
|
||
build_number = models.CharField(max_length=20, blank=True, null=True)
|
||
is_latest = models.BooleanField(default=False, help_text="最新版フラグ")
|
||
is_required = models.BooleanField(default=False, help_text="強制更新フラグ")
|
||
update_message = models.TextField(blank=True, null=True, help_text="ユーザー向け更新メッセージ")
|
||
download_url = models.URLField(blank=True, null=True, help_text="アプリストアURL")
|
||
release_date = models.DateTimeField(default=timezone.now)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
db_table = 'app_versions'
|
||
unique_together = ['version', 'platform']
|
||
indexes = [
|
||
models.Index(fields=['platform'], name='idx_app_versions_platform'),
|
||
models.Index(
|
||
fields=['is_latest'],
|
||
condition=models.Q(is_latest=True),
|
||
name='idx_app_versions_latest_true'
|
||
),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.platform} {self.version}"
|
||
|
||
def save(self, *args, **kwargs):
|
||
"""最新版フラグが設定された場合、同一プラットフォームの他のバージョンを非最新にする"""
|
||
if self.is_latest:
|
||
AppVersion.objects.filter(
|
||
platform=self.platform,
|
||
is_latest=True
|
||
).exclude(pk=self.pk).update(is_latest=False)
|
||
super().save(*args, **kwargs)
|
||
|
||
@classmethod
|
||
def compare_versions(cls, version1, version2):
|
||
"""セマンティックバージョンの比較"""
|
||
def version_tuple(v):
|
||
return tuple(map(int, v.split('.')))
|
||
|
||
v1 = version_tuple(version1)
|
||
v2 = version_tuple(version2)
|
||
|
||
if v1 < v2:
|
||
return -1
|
||
elif v1 > v2:
|
||
return 1
|
||
else:
|
||
return 0
|
||
|
||
@classmethod
|
||
def get_latest_version(cls, platform):
|
||
"""指定プラットフォームの最新バージョンを取得"""
|
||
try:
|
||
return cls.objects.filter(platform=platform, is_latest=True).first()
|
||
except cls.DoesNotExist:
|
||
return None
|
||
|
||
|
||
class CheckinExtended(models.Model):
|
||
"""チェックイン拡張情報モデル"""
|
||
|
||
VALIDATION_STATUS_CHOICES = [
|
||
('pending', 'Pending'),
|
||
('approved', 'Approved'),
|
||
('rejected', 'Rejected'),
|
||
('requires_review', 'Requires Review'),
|
||
]
|
||
|
||
gpslog = models.ForeignKey('GpsCheckin', on_delete=models.CASCADE, related_name='extended_info')
|
||
|
||
# GPS拡張情報
|
||
gps_latitude = models.DecimalField(max_digits=10, decimal_places=8, null=True, blank=True)
|
||
gps_longitude = models.DecimalField(max_digits=11, decimal_places=8, null=True, blank=True)
|
||
gps_accuracy = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True, help_text="GPS精度(メートル)")
|
||
gps_timestamp = models.DateTimeField(null=True, blank=True)
|
||
|
||
# カメラメタデータ
|
||
camera_capture_time = models.DateTimeField(null=True, blank=True)
|
||
device_info = models.TextField(blank=True, null=True)
|
||
|
||
# 審査・検証情報
|
||
validation_status = models.CharField(
|
||
max_length=20,
|
||
choices=VALIDATION_STATUS_CHOICES,
|
||
default='pending'
|
||
)
|
||
validation_comment = models.TextField(blank=True, null=True)
|
||
validated_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True)
|
||
validated_at = models.DateTimeField(null=True, blank=True)
|
||
|
||
# スコア情報
|
||
bonus_points = models.IntegerField(default=0)
|
||
scoring_breakdown = JSONField(default=dict, blank=True)
|
||
|
||
# システム情報
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
db_table = 'rog_checkin_extended'
|
||
indexes = [
|
||
models.Index(fields=['validation_status'], name='idx_checkin_ext_valid'),
|
||
models.Index(fields=['created_at'], name='idx_checkin_ext_created'),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"CheckinExtended {self.gpslog_id} - {self.validation_status}"
|
||
|
||
|
||
class UploadedImage(models.Model):
|
||
"""画像アップロード管理モデル - マルチアップロード対応"""
|
||
|
||
UPLOAD_SOURCE_CHOICES = [
|
||
('direct', 'Direct'),
|
||
('sharing_intent', 'Sharing Intent'),
|
||
('bulk_upload', 'Bulk Upload'),
|
||
]
|
||
|
||
PLATFORM_CHOICES = [
|
||
('ios', 'iOS'),
|
||
('android', 'Android'),
|
||
('web', 'Web'),
|
||
]
|
||
|
||
PROCESSING_STATUS_CHOICES = [
|
||
('uploaded', 'Uploaded'),
|
||
('processing', 'Processing'),
|
||
('processed', 'Processed'),
|
||
('failed', 'Failed'),
|
||
]
|
||
|
||
MIME_TYPE_CHOICES = [
|
||
('image/jpeg', 'JPEG'),
|
||
('image/png', 'PNG'),
|
||
('image/heic', 'HEIC'),
|
||
('image/webp', 'WebP'),
|
||
]
|
||
|
||
# 基本情報
|
||
original_filename = models.CharField(max_length=255)
|
||
server_filename = models.CharField(max_length=255, unique=True)
|
||
file_url = models.URLField()
|
||
file_size = models.BigIntegerField()
|
||
mime_type = models.CharField(max_length=50, choices=MIME_TYPE_CHOICES)
|
||
|
||
# 関連情報
|
||
event_code = models.CharField(max_length=50, blank=True, null=True)
|
||
team_name = models.CharField(max_length=255, blank=True, null=True)
|
||
cp_number = models.IntegerField(blank=True, null=True)
|
||
|
||
# アップロード情報
|
||
upload_source = models.CharField(max_length=50, choices=UPLOAD_SOURCE_CHOICES, default='direct')
|
||
device_platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES, blank=True, null=True)
|
||
|
||
# メタデータ
|
||
capture_timestamp = models.DateTimeField(blank=True, null=True)
|
||
upload_timestamp = models.DateTimeField(auto_now_add=True)
|
||
device_info = models.TextField(blank=True, null=True)
|
||
|
||
# 処理状況
|
||
processing_status = models.CharField(max_length=20, choices=PROCESSING_STATUS_CHOICES, default='uploaded')
|
||
thumbnail_url = models.URLField(blank=True, null=True)
|
||
|
||
# 外部キー
|
||
gpslog = models.ForeignKey('GpsCheckin', on_delete=models.SET_NULL, null=True, blank=True)
|
||
entry = models.ForeignKey('Entry', on_delete=models.SET_NULL, null=True, blank=True)
|
||
|
||
# システム情報
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
db_table = 'rog_uploaded_images'
|
||
indexes = [
|
||
models.Index(fields=['event_code', 'team_name'], name='idx_uploaded_event_team'),
|
||
models.Index(fields=['cp_number'], name='idx_uploaded_cp_number'),
|
||
models.Index(fields=['upload_timestamp'], name='idx_uploaded_timestamp'),
|
||
models.Index(fields=['processing_status'], name='idx_uploaded_status'),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.original_filename} - {self.event_code} - CP{self.cp_number}"
|
||
|
||
def clean(self):
|
||
"""バリデーション"""
|
||
if self.file_size and (self.file_size <= 0 or self.file_size > 10485760): # 10MB
|
||
raise ValidationError("ファイルサイズは10MB以下である必要があります")
|
||
|
||
@property
|
||
def file_size_mb(self):
|
||
"""ファイルサイズをMB単位で取得"""
|
||
return round(self.file_size / 1024 / 1024, 2) if self.file_size else 0
|
||
|
||
|
||
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(null=True, blank=True)
|
||
deadlineDateTime = models.DateTimeField(null=True, blank=True)
|
||
|
||
#// Added @2024-10-21
|
||
public = models.BooleanField(default=False)
|
||
|
||
# Status field for enhanced event management (2025-08-27)
|
||
STATUS_CHOICES = [
|
||
('public', 'Public'),
|
||
('private', 'Private'),
|
||
('draft', 'Draft'),
|
||
('closed', 'Closed'),
|
||
]
|
||
status = models.CharField(
|
||
max_length=20,
|
||
choices=STATUS_CHOICES,
|
||
default='draft',
|
||
help_text="イベントステータス"
|
||
)
|
||
|
||
hour_3 = models.BooleanField(default=False)
|
||
hour_5 = models.BooleanField(default=True)
|
||
class_general = models.BooleanField(default=True)
|
||
class_family = models.BooleanField(default=True)
|
||
class_solo_male = models.BooleanField(default=True)
|
||
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):
|
||
if not self.deadlineDateTime:
|
||
self.deadlineDateTime = self.end_datetime #- timedelta(days=7)
|
||
|
||
# publicフィールドからstatusフィールドへの自動移行
|
||
if self.pk is None and self.status == 'draft': # 新規作成時
|
||
if self.public:
|
||
self.status = 'public'
|
||
|
||
super().save(*args, **kwargs)
|
||
|
||
@property
|
||
def deadline_datetime(self):
|
||
"""API応答用のフィールド名統一"""
|
||
return self.deadlineDateTime
|
||
|
||
def is_accessible_by_user(self, user):
|
||
"""ユーザーがこのイベントにアクセス可能かチェック"""
|
||
if self.status == 'public':
|
||
return True
|
||
elif self.status == 'private':
|
||
# スタッフ権限チェック(後で実装)
|
||
return hasattr(user, 'staff_privileges') and user.staff_privileges
|
||
elif self.status == 'draft':
|
||
# ドラフトは管理者のみ
|
||
return user.is_staff or user.is_superuser
|
||
elif self.status == 'closed':
|
||
return False
|
||
return False
|
||
|
||
def get_default_end_datetime():
|
||
"""デフォルトの終了日時を取得"""
|
||
from datetime import timedelta
|
||
return timezone.now() + timedelta(days=1)
|
||
|
||
class NewEvent(models.Model):
|
||
event_name = models.CharField(max_length=255, primary_key=True)
|
||
start_datetime = models.DateTimeField(default=timezone.now)
|
||
end_datetime = models.DateTimeField(default=get_default_end_datetime)
|
||
|
||
def __str__(self):
|
||
return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}"
|
||
|
||
def get_default_category():
|
||
"""デフォルトカテゴリーを取得または作成"""
|
||
try:
|
||
return NewCategory.objects.get_or_create(
|
||
category_name="Default Category",
|
||
defaults={'category_number': 1}
|
||
)[0].id
|
||
except Exception:
|
||
return 1 # フェイルセーフ
|
||
|
||
def get_default_multipoint():
|
||
"""デフォルトのMultiPointを取得"""
|
||
try:
|
||
from django.contrib.gis.geos import MultiPoint, Point
|
||
return MultiPoint([Point(0, 0)])
|
||
except Exception:
|
||
return None
|
||
|
||
def get_default_event():
|
||
"""デフォルトイベントを取得または作成"""
|
||
try:
|
||
from datetime import timedelta
|
||
return NewEvent2.objects.get_or_create(
|
||
event_name="Default Event",
|
||
defaults={
|
||
'start_datetime': timezone.now(),
|
||
'end_datetime': timezone.now() + timedelta(days=1)
|
||
}
|
||
)[0].id
|
||
except Exception:
|
||
return 1 # フェイルセーフ
|
||
|
||
|
||
class Team(models.Model):
|
||
# 既存フィールド
|
||
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:
|
||
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):
|
||
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):
|
||
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='members')
|
||
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
|
||
firstname = models.CharField(max_length=255, blank=True, null=True)
|
||
lastname = models.CharField(max_length=255, blank=True, null=True)
|
||
date_of_birth = models.DateField(null=True, blank=True)
|
||
female = models.BooleanField(default=False)
|
||
|
||
is_temporary = models.BooleanField(default=False) # Akira 2024-7-24
|
||
|
||
class Meta:
|
||
unique_together = ('team', 'user')
|
||
|
||
def __str__(self):
|
||
return f"{self.team.team_name} - {self.user.lastname} {self.user.firstname}"
|
||
|
||
#
|
||
class Category(models.Model):
|
||
category_name = models.CharField(max_length=255, primary_key=True)
|
||
category_number = models.IntegerField(default=0)
|
||
duration = models.DurationField(default=timedelta(hours=5))
|
||
num_of_member = models.IntegerField(default=1)
|
||
family = models.BooleanField(default=False)
|
||
female = models.BooleanField(default=False)
|
||
|
||
class Meta:
|
||
unique_together = ('category_name','category_number')
|
||
|
||
def __str__(self):
|
||
hours = self.duration.total_seconds() // 3600
|
||
return f"{self.category_name} - {self.category_number} ({int(hours)}時間)"
|
||
|
||
@property
|
||
def hours(self):
|
||
return self.duration.total_seconds() // 3600
|
||
|
||
class NewCategory(models.Model):
|
||
category_name = models.CharField(max_length=255, unique=True)
|
||
category_number = models.IntegerField(default=0)
|
||
duration = models.DurationField(default=timedelta(hours=5))
|
||
num_of_member = models.IntegerField(default=1)
|
||
family = models.BooleanField(default=False)
|
||
female = models.BooleanField(default=False)
|
||
trial = models.BooleanField(default=False)
|
||
|
||
class Meta:
|
||
unique_together = ('category_name','category_number')
|
||
|
||
def __str__(self):
|
||
hours = self.duration.total_seconds() // 3600
|
||
return f"{self.category_name} - {self.category_number} ({int(hours)}時間)"
|
||
|
||
@property
|
||
def hours(self):
|
||
return self.duration.total_seconds() // 3600
|
||
|
||
class Entry(models.Model):
|
||
team = models.ForeignKey(Team, on_delete=models.CASCADE)
|
||
event = models.ForeignKey(NewEvent2, on_delete=models.CASCADE, default=get_default_event)
|
||
category = models.ForeignKey(NewCategory, on_delete=models.CASCADE, default=get_default_category)
|
||
date = models.DateTimeField(default=timezone.now)
|
||
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE,blank=True, null=True) # Akira 2024-7-24
|
||
zekken_number = models.IntegerField(default=0)
|
||
zekken_label = models.CharField(max_length=255, blank=True, null=True)
|
||
is_active = models.BooleanField(default=True) # 新しく追加
|
||
hasParticipated = models.BooleanField(default=False) # 新しく追加
|
||
hasGoaled = models.BooleanField(default=False) # 新しく追加
|
||
|
||
# API変更要求書対応: スタッフ権限管理 (2025-08-27)
|
||
staff_privileges = models.BooleanField(default=False, help_text="スタッフ権限フラグ")
|
||
can_access_private_events = models.BooleanField(default=False, help_text="非公開イベント参加権限")
|
||
|
||
VALIDATION_STATUS_CHOICES = [
|
||
('approved', 'Approved'),
|
||
('pending', 'Pending'),
|
||
('rejected', 'Rejected'),
|
||
]
|
||
team_validation_status = models.CharField(
|
||
max_length=20,
|
||
choices=VALIDATION_STATUS_CHOICES,
|
||
default='approved',
|
||
help_text="チーム承認状況"
|
||
)
|
||
|
||
|
||
class Meta:
|
||
unique_together = ('zekken_number', 'event','date')
|
||
|
||
def __str__(self):
|
||
return f"{self.zekken_number} - {self.team.team_name} - {self.event.event_name} - {self.date}"
|
||
|
||
def clean(self):
|
||
super().clean()
|
||
if self.event and self.category and self.date:
|
||
start = self.event.start_datetime
|
||
end = self.event.end_datetime #- self.category.duration
|
||
if not (start.date() <= self.date.date() <= end.date()):
|
||
raise ValidationError({
|
||
'date': f'日時{self.date}は{start.date()}から{end.date()}の間である必要があります。'
|
||
})
|
||
|
||
# メンバーの年齢と性別をチェック
|
||
if self.team: # and not self.team.members.exists():
|
||
members = self.team.members.all() # membersを適切に取得
|
||
if not members.exists():
|
||
raise ValidationError({'team': 'チームにメンバーが登録されていません。'})
|
||
|
||
#members = Member.objects.filter(team=self.team)
|
||
#if not members.exists():
|
||
# # ここで、owner をMemberに登録する。 Akira 2024-7-24
|
||
# raise ValidationError("チームにメンバーが登録されていません。")
|
||
|
||
adults = [m for m in members if self.is_adult(m.user.date_of_birth)]
|
||
children = [m for m in members if self.is_child(m.user.date_of_birth)]
|
||
teenagers = [m for m in members if self.is_teenager(m.user.date_of_birth)]
|
||
|
||
if self.category.family:
|
||
if not (adults and children):
|
||
raise ValidationError("ファミリーカテゴリーには、18歳以上のメンバーと小学生以下のメンバーが各1名以上必要です。")
|
||
else:
|
||
if not adults:
|
||
raise ValidationError("18歳以上のメンバーが1名以上必要です。")
|
||
|
||
if children and not self.category.trial:
|
||
raise ValidationError("ファミリーカテゴリーまたはお試し以外では、小学生以下のメンバーは参加できません。")
|
||
|
||
if self.category.num_of_member == 1:
|
||
if len(members) != 1:
|
||
raise ValidationError("このカテゴリーはソロ参加のみ可能です。")
|
||
if not adults:
|
||
raise ValidationError("ソロ参加は18歳以上のみ可能です。")
|
||
if self.category.female and not members[0].user.female:
|
||
raise ValidationError("このカテゴリーは女性のみ参加可能です。")
|
||
if not self.category.female and members[0].user.female:
|
||
raise ValidationError("このカテゴリーは男性のみ参加可能です。")
|
||
|
||
|
||
if len(members) > self.category.num_of_member:
|
||
raise ValidationError(f"このカテゴリーは{self.category.num_of_member}名までの参加が必要です。")
|
||
|
||
|
||
def save(self, *args, **kwargs):
|
||
self.full_clean()
|
||
super().save(*args, **kwargs)
|
||
|
||
@staticmethod
|
||
def is_adult(birth_date):
|
||
today = date.today()
|
||
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
|
||
return age >= 18
|
||
|
||
@staticmethod
|
||
def is_child(birth_date):
|
||
today = date.today()
|
||
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
|
||
return age <= 12
|
||
|
||
@staticmethod
|
||
def is_teenager(birth_date):
|
||
today = date.today()
|
||
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
|
||
return 13 <= age <= 17
|
||
|
||
|
||
class EntryMember(models.Model):
|
||
entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
|
||
member = models.ForeignKey(Member, on_delete=models.CASCADE)
|
||
is_temporary = models.BooleanField(default=False) # Akira 2024-7-24
|
||
|
||
class Meta:
|
||
unique_together = ('entry', 'member')
|
||
|
||
def __str__(self):
|
||
return f"{self.entry.team.zekken_number} - {self.member.user.lastname} {self.member.user.firstname}"
|
||
|
||
|
||
class GoalImages(models.Model):
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
goalimage = models.FileField(upload_to='goals/%y%m%d', blank=True, null=True)
|
||
goaltime = models.DateTimeField(_("Goal time"), blank=True, null=True,auto_now=False, auto_now_add=False)
|
||
team_name = models.CharField(_("Team name"), max_length=255)
|
||
event_code = models.CharField(_("event code"), max_length=255)
|
||
cp_number = models.IntegerField(_("CP numner"))
|
||
zekken_number = models.TextField(
|
||
null=True, # False にする
|
||
blank=True, # False にする
|
||
help_text="ゼッケン番号"
|
||
)
|
||
|
||
class CheckinImages(models.Model):
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
checkinimage = models.FileField(upload_to='checkin/%y%m%d', blank=True, null=True)
|
||
checkintime = models.DateTimeField(_("Goal time"), auto_now=False, auto_now_add=False)
|
||
team_name = models.CharField(_("Team name"), max_length=255)
|
||
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(
|
||
null=True,
|
||
blank=True,
|
||
help_text="イベントID"
|
||
)
|
||
event_code = models.TextField(
|
||
null=False,
|
||
default='',
|
||
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="ポイント:このチェックインによる獲得ポイント。通常ポイントと買い物ポイントは分離される。ゴールの場合には減点なども含む。"
|
||
)
|
||
|
||
# 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_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'
|
||
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}-buy:{self.buy_flag}-valid:{self.validate_location}-point:{self.points}"
|
||
|
||
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)
|
||
phone=models.CharField(_('Phone Number'), max_length=55)
|
||
first_name=models.CharField(_('First Name'), max_length=255)
|
||
middle_name=models.CharField(_('Middle Name'), max_length=255, blank=True, null=True)
|
||
last_name=models.CharField(_('last_name'), max_length=255)
|
||
nickname=models.CharField(_('Nickname'), max_length=255, blank=True, null=True)
|
||
country=models.CharField(_('Country'), max_length=255, default='Japan')
|
||
language=models.CharField(_('Language'), max_length=255, default='Japanese')
|
||
prefecture=models.CharField(_('Prefecture'), max_length=255, blank=True, null=True)
|
||
sex=models.CharField(_('Sex'), max_length=255, default='unknown', blank=True, null=True)
|
||
birthyear=models.IntegerField(_('Birth year'), blank=True, null=True)
|
||
family_structure =models.IntegerField(_('Family Structure'), blank=True, null=True)
|
||
introducer = models.ForeignKey(CustomUser, related_name='introduced_uesr', on_delete=models.DO_NOTHING)
|
||
level= models.IntegerField(_('Level'), blank=True, null=True, default=0)
|
||
paid=models.BooleanField(_("Is Paid"),default=False)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="roguser_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
|
||
class SystemSettings(models.Model):
|
||
setting_name=models.CharField(_('Settings Name'), max_length=255)
|
||
version=models.CharField(_('Version'), max_length=10, blank=True, null=True)
|
||
effective_date=models.DateTimeField()
|
||
end_date=models.DateTimeField()
|
||
parammeters=models.CharField(_('Parameters'), max_length=512)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="system_setting_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
class Location(models.Model):
|
||
location_id=models.IntegerField(_('Location id'), blank=True, null=True, db_index=True)
|
||
sub_loc_id=models.CharField(_('Sub location id'), max_length=2048, blank=True, null=True)
|
||
cp=models.FloatField(_('Check Point'), blank=False, null=True, default=0)
|
||
location_name=models.CharField(_('Location Name'), max_length=2048, default="--- 場所をお願いします --")
|
||
category=models.CharField(_('Category'), max_length=2048, blank=True, null=True, db_index=True)
|
||
subcategory=models.CharField(_('Sub Category'), max_length=2048, blank=True, null=True)
|
||
zip=models.CharField(_('Zip code'), max_length=12, blank=True, null=True)
|
||
address = models.CharField(_('Address'), max_length=2048, blank=True, null=True)
|
||
prefecture = models.CharField(_('Prefecture'), max_length=2048, blank=True, null=True)
|
||
area= models.CharField(_('Area'), max_length=2048, blank=True, null=True)
|
||
city= models.CharField(_('City'), max_length=2048, blank=True, null=True)
|
||
latitude = models.FloatField('Latitude', blank=True, null=True)
|
||
longitude = models.FloatField('Latitude', blank=True, null=True)
|
||
photos=models.CharField(_('Photos'), max_length=2048, blank=True, null=True)
|
||
videos=models.CharField(_('Videos'), max_length=2048, blank=True, null=True)
|
||
webcontents=models.CharField(_('Web Content'), max_length=2048, blank=True, null=True)
|
||
status=models.CharField(_('Status'),max_length=2048, blank=True, null=True)
|
||
portal=models.CharField(_('Portal'), max_length=2048,blank=True, null=True)
|
||
group=models.CharField(_('Group'), max_length=2048,blank=True, null=True, db_index=True)
|
||
phone=models.CharField(_('Phone'), max_length=2048,blank=True, null=True)
|
||
fax=models.CharField(_('Fax'), max_length=2048, blank=True, null=True)
|
||
email=models.EmailField(_('Email'), max_length=2048,blank=True, null=True)
|
||
facility=models.CharField(_('Facility'), max_length=2048, blank=True, null=True)
|
||
remark=models.CharField(_('Remarks'), max_length=2048, blank=True, null=True)
|
||
tags=models.CharField(_('Tags'), max_length=2048, blank=True, null=True)
|
||
event_name = models.CharField(_('Event name'), max_length=2048, blank=True, null=True, db_index=True)
|
||
event_active = models.BooleanField(_("Is Event active"),default=True, db_index=True)
|
||
hidden_location = models.BooleanField(_("Is Hidden Location"),default=False)
|
||
auto_checkin = models.BooleanField(_("Is AutoCheckin"),default=False)
|
||
checkin_radius = models.FloatField(_("Checkin radious"), blank=True, null=True, default=15.0)
|
||
checkin_point = models.FloatField(_("Checkin Point"), blank=True, null=True, default=10)
|
||
buy_point = models.FloatField(_("buy Point"), blank=True, null=True, default=0)
|
||
evaluation_value = models.CharField(_('Evaluation value (評価)'),max_length=2048, blank=True, null=True)
|
||
shop_closed = models.BooleanField(_("Shop Closed (休業)"),default=False)
|
||
shop_shutdown = models.BooleanField(_("Shop Shutdown (閉業)"),default=False)
|
||
opening_hours_mon = models.CharField(_("Opening hours monday (月曜)"),max_length=512, blank=True, null=True)
|
||
opening_hours_tue = models.CharField(_("Opening hours tuesday (火曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_wed = models.CharField(_("Opening hours wednesday (水曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_thu = models.CharField(_("Opening hours thursday (木曜)"), max_length=512, blank=True, null=True)
|
||
opening_hours_fri = models.CharField(_("Opening hours frinday (金曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_sat = models.CharField(_("Opening hours saturday (土曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_sun = models.CharField(_("Opening hours sunday (日曜)"),max_length=512, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=2048, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="location_updated_user", on_delete=models.DO_NOTHING,blank=True, null=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
geom=models.MultiPointField(srid=4326, default=get_default_multipoint)
|
||
|
||
class Meta:
|
||
indexes = [
|
||
GistIndex(fields=['geom']),
|
||
]
|
||
|
||
def __str__(self):
|
||
return self.location_name
|
||
|
||
|
||
class templocation(models.Model):
|
||
location_id=models.IntegerField(_('Location id'), blank=True, null=True)
|
||
sub_loc_id=models.CharField(_('Sub location id'), max_length=2048, blank=True, null=True)
|
||
cp=models.FloatField(_('Check Point'), blank=False, null=True, default=0)
|
||
location_name=models.CharField(_('Location Name'), max_length=2048, default="--- 場所をお願いします --")
|
||
category=models.CharField(_('Category'), max_length=2048, blank=True, null=True)
|
||
subcategory=models.CharField(_('Sub Category'), max_length=2048, blank=True, null=True)
|
||
zip=models.CharField(_('Zip code'), max_length=12, blank=True, null=True)
|
||
address = models.CharField(_('Address'), max_length=2048, blank=True, null=True)
|
||
prefecture = models.CharField(_('Prefecture'), max_length=2048, blank=True, null=True)
|
||
area= models.CharField(_('Area'), max_length=2048, blank=True, null=True)
|
||
city= models.CharField(_('City'), max_length=2048, blank=True, null=True)
|
||
latitude = models.FloatField('Latitude', blank=True, null=True)
|
||
longitude = models.FloatField('Latitude', blank=True, null=True)
|
||
photos=models.CharField(_('Photos'), max_length=2048, blank=True, null=True)
|
||
videos=models.CharField(_('Videos'), max_length=2048, blank=True, null=True)
|
||
webcontents=models.CharField(_('Web Content'), max_length=2048, blank=True, null=True)
|
||
status=models.CharField(_('Status'),max_length=2048, blank=True, null=True)
|
||
portal=models.CharField(_('Portal'), max_length=2048,blank=True, null=True)
|
||
group=models.CharField(_('Group'), max_length=2048,blank=True, null=True)
|
||
phone=models.CharField(_('Phone'), max_length=2048,blank=True, null=True)
|
||
fax=models.CharField(_('Fax'), max_length=2048, blank=True, null=True)
|
||
email=models.EmailField(_('Email'), max_length=2048,blank=True, null=True)
|
||
facility=models.CharField(_('Facility'), max_length=2048, blank=True, null=True)
|
||
remark=models.CharField(_('Remarks'), max_length=2048, blank=True, null=True)
|
||
tags=models.CharField(_('Tags'), max_length=2048, blank=True, null=True)
|
||
event_name = models.CharField(_('Event name'), max_length=2048, blank=True, null=True)
|
||
event_active = models.BooleanField(_("Is Event active"),default=True)
|
||
hidden_location = models.BooleanField(_("Is Hidden Location"),default=False)
|
||
auto_checkin = models.BooleanField(_("Is AutoCheckin"),default=False)
|
||
checkin_radius = models.FloatField(_("Checkin radious"), blank=True, null=True, default=15.0)
|
||
checkin_point = models.FloatField(_("Checkin Point"), blank=True, null=True, default=10)
|
||
buy_point = models.FloatField(_("buy Point"), blank=True, null=True, default=0)
|
||
evaluation_value = models.CharField(_('Evaluation value (評価)'),max_length=2048, blank=True, null=True)
|
||
shop_closed = models.BooleanField(_("Shop Closed (休業)"),default=False)
|
||
shop_shutdown = models.BooleanField(_("Shop Shutdown (閉業)"),default=False)
|
||
opening_hours_mon = models.CharField(_("Opening hours monday (月曜)"),max_length=512, blank=True, null=True)
|
||
opening_hours_tue = models.CharField(_("Opening hours tuesday (火曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_wed = models.CharField(_("Opening hours wednesday (水曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_thu = models.CharField(_("Opening hours thursday (木曜)"), max_length=512, blank=True, null=True)
|
||
opening_hours_fri = models.CharField(_("Opening hours frinday (金曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_sat = models.CharField(_("Opening hours saturday (土曜)"), max_length=512,blank=True, null=True)
|
||
opening_hours_sun = models.CharField(_("Opening hours sunday (日曜)"),max_length=512, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=2048, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="temp_location_updated_user", on_delete=models.DO_NOTHING,blank=True, null=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
geom=models.MultiPointField(srid=4326)
|
||
|
||
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)
|
||
category=models.CharField(_('Category'), max_length=255, blank=True, null=True)
|
||
zip=models.CharField(_('Zip code'), max_length=12, blank=True, null=True)
|
||
address = models.CharField(_('Address'), max_length=512, blank=True, null=True)
|
||
prefecture = models.CharField(_('Prefecture'), max_length=255, blank=True, null=True)
|
||
area= models.CharField(_('Area'), max_length=255, blank=True, null=True)
|
||
city= models.CharField(_('City'), max_length=255, blank=True, null=True)
|
||
photos=models.CharField(_('Phptos'), max_length=255, blank=True, null=True)
|
||
videos=models.CharField(_('Videos'), max_length=255, blank=True, null=True)
|
||
webcontents=models.CharField(_('Web Content'), max_length=255, blank=True, null=True)
|
||
status=models.CharField(_('Status'),max_length=255, blank=True, null=True)
|
||
portal=models.CharField(_('Portal'), max_length=255,blank=True, null=True)
|
||
group=models.CharField(_('Group'), max_length=255,blank=True, null=True)
|
||
phone=models.CharField(_('Phone'), max_length=255,blank=True, null=True)
|
||
fax=models.CharField(_('Fax'), max_length=255, blank=True, null=True)
|
||
email=models.EmailField(_('Email'), max_length=255,blank=True, null=True)
|
||
facility=models.CharField(_('Facility'), max_length=255, blank=True, null=True)
|
||
remark=models.CharField(_('Remarks'), max_length=255, blank=True, null=True)
|
||
tags=models.CharField(_('Tags'), max_length=512, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="location_line_updated_user", on_delete=models.DO_NOTHING,blank=True, null=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
geom=models.MultiLineStringField(srid=4326, blank=True, null=True)
|
||
|
||
def __str__(self):
|
||
return str(self.location_id)
|
||
|
||
|
||
|
||
class Location_polygon(models.Model):
|
||
location_id=models.IntegerField(_('Location id'), blank=True, null=True)
|
||
location_name=models.CharField(_('Location Name'), max_length=255)
|
||
category=models.CharField(_('Category'), max_length=255, blank=True, null=True)
|
||
zip=models.CharField(_('Zip code'), max_length=12, blank=True, null=True)
|
||
address = models.CharField(_('Address'), max_length=512, blank=True, null=True)
|
||
prefecture = models.CharField(_('Prefecture'), max_length=255, blank=True, null=True)
|
||
area= models.CharField(_('Area'), max_length=255, blank=True, null=True)
|
||
city= models.CharField(_('City'), max_length=255, blank=True, null=True)
|
||
photos=models.CharField(_('Phptos'), max_length=255, blank=True, null=True)
|
||
videos=models.CharField(_('Videos'), max_length=255, blank=True, null=True)
|
||
webcontents=models.CharField(_('Web Content'), max_length=255, blank=True, null=True)
|
||
status=models.CharField(_('Status'),max_length=255, blank=True, null=True)
|
||
portal=models.CharField(_('Portal'), max_length=255,blank=True, null=True)
|
||
group=models.CharField(_('Group'), max_length=255,blank=True, null=True)
|
||
phone=models.CharField(_('Phone'), max_length=255,blank=True, null=True)
|
||
fax=models.CharField(_('Fax'), max_length=255, blank=True, null=True)
|
||
email=models.EmailField(_('Email'), max_length=255,blank=True, null=True)
|
||
facility=models.CharField(_('Facility'), max_length=255, blank=True, null=True)
|
||
remark=models.CharField(_('Remarks'), max_length=255, blank=True, null=True)
|
||
tags=models.CharField(_('Tags'), max_length=512, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="location_polygon_updated_user", on_delete=models.DO_NOTHING,blank=True, null=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
geom=models.MultiPolygonField(srid=4326, blank=True, null=True)
|
||
|
||
def __str__(self):
|
||
return str(self.location_name)
|
||
|
||
|
||
class UserTracks(models.Model):
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
geom=models.MultiPointField(srid=4326)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
|
||
|
||
|
||
EVENT_STATUS = (
|
||
("PREPARING", _("Preparing")),
|
||
("PROMOTION", _("Promotion")),
|
||
("EVENT", _("Event")),
|
||
("END", _("End"))
|
||
)
|
||
|
||
|
||
class Event(models.Model):
|
||
user=models.ManyToManyField(CustomUser, through='EventUser', related_name='even')
|
||
tagname=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
status=models.CharField(max_length=256, choices=EVENT_STATUS)
|
||
price=models.IntegerField(_('Paid Amount'), default=0)
|
||
promotion_date=models.DateTimeField(_('Promotion date'), blank=True, null=True)
|
||
event_start=models.DateTimeField(_('Promotion date'), blank=True, null=True)
|
||
event_end=models.DateTimeField(_('Promotion date'), blank=True, null=True)
|
||
remark=models.CharField(max_length=256, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="event_updated_user", on_delete=models.DO_NOTHING,blank=True, null=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
class EventUser(models.Model):
|
||
user=models.ForeignKey(CustomUser,on_delete=models.CASCADE, related_name='user')
|
||
event=models.ForeignKey(Event, on_delete=models.CASCADE, related_name='event')
|
||
|
||
|
||
ROG_STATUS = (
|
||
("REGISTERED", _("Registered")),
|
||
("ACCEPTED", _("accepted")),
|
||
("PAID", _("paid")),
|
||
("JOINED", _("joined")),
|
||
("CANCELED", _("Canceled"))
|
||
)
|
||
|
||
class JoinedEvent(models.Model):
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
tagname=models.CharField(_('Tag Name'), max_length=255, blank=True, null=True)
|
||
status=models.CharField(max_length=256, choices=ROG_STATUS)
|
||
registrationid=models.CharField(_('Registration Id'), max_length=56)
|
||
payment_code=models.CharField(_('Payment Code'), max_length=255)
|
||
paid=models.IntegerField(_('Paid Amount'), default=0)
|
||
remark=models.CharField(_('Remark'), max_length=255, blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="joined_event_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
|
||
class Favorite(models.Model):
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
location=models.ForeignKey(Location, on_delete=models.CASCADE)
|
||
good=models.IntegerField(_('Good'), default=0)
|
||
favorite=models.IntegerField(_('Favorite'), default=0)
|
||
evaluation=models.IntegerField(_('Evaluation'), default=0)
|
||
number_visit=models.IntegerField(_('Good'), default=0)
|
||
last_visited=models.DateTimeField(_('Last Visited'), blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512, blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="favorite_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
|
||
TRAVEL_CATEGORY = (
|
||
("PRIVATE", _("Private")),
|
||
("GROUP", _("Group")),
|
||
("AGENT", _("Agent")),
|
||
("ROGAINING", _("Rogaining"))
|
||
)
|
||
|
||
|
||
|
||
class TravelList(models.Model):
|
||
travel_id= models.IntegerField(_('Travel Id'))
|
||
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
|
||
start_date=models.DateTimeField(_('Start date') ,blank=True, null=True)
|
||
finish_date=models.DateTimeField(_('End date') ,blank=True, null=True)
|
||
category=models.CharField(max_length=256, choices=TRAVEL_CATEGORY)
|
||
title=models.CharField(_('Title'), max_length=255)
|
||
transportation=models.CharField(_('Transpotation'), max_length=255 ,blank=True, null=True)
|
||
moving_distance=models.IntegerField(blank=True, null=True)
|
||
duration=models.DurationField(_('Duration') ,blank=True, null=True)
|
||
eta=models.DateTimeField(blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512 ,blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="travel_list_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
class TravelPoint(models.Model):
|
||
travel_list= models.ForeignKey(TravelList, on_delete=models.DO_NOTHING)
|
||
location=models.ForeignKey(Location, on_delete=models.CASCADE)
|
||
distance=models.FloatField(blank=True, null=True)
|
||
transportation=models.CharField(_('Transpotation'), max_length=255 ,blank=True, null=True)
|
||
eta=models.DateTimeField(blank=True, null=True)
|
||
order_number=models.IntegerField(blank=True, null=True)
|
||
parammeters=models.CharField(_('Parameters'), max_length=512 ,blank=True, null=True)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_user=models.ForeignKey(CustomUser, related_name="travelpoint_updated_user", on_delete=models.DO_NOTHING)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
class Useractions(models.Model):
|
||
user=models.ForeignKey(CustomUser, related_name="action_user", on_delete=models.CASCADE)
|
||
location=models.ForeignKey(Location, related_name="action_location", on_delete=models.CASCADE)
|
||
wanttogo=models.BooleanField(default=False)
|
||
like=models.BooleanField(default=False)
|
||
checkin=models.BooleanField(default=False)
|
||
checkinimage=models.FileField(upload_to='%y%m%d', blank=True, null=True)
|
||
order =models.IntegerField(default=0)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
class TestModel(models.Model):
|
||
testbane=models.CharField(_("test field"), max_length=355)
|
||
wanttogo=models.BooleanField(default=False)
|
||
like=models.BooleanField(default=False)
|
||
checkin=models.BooleanField(default=False)
|
||
created_at=models.DateTimeField(auto_now_add=True)
|
||
last_updated_at=models.DateTimeField(auto_now=True)
|
||
|
||
|
||
def getTableForModel(tbl):
|
||
if tbl == 1:
|
||
return templocation.objects.model._meta.db_table;
|
||
elif tbl == 2:
|
||
return Location_line.objects.model._meta.db_table;
|
||
else:
|
||
return Location_polygon.objects.model._meta.db_table;
|
||
|
||
def getTempMappingforModel(tbl, shp):
|
||
if tbl == 1:
|
||
return LayerMapping(templocation, shp, location_mapping, transform=False)
|
||
elif tbl == 2:
|
||
return LayerMapping(Location_line, shp, location_line_mapping, transform=False)
|
||
else:
|
||
return LayerMapping(Location_polygon, shp, location_polygon_mapping, transform=False)
|
||
|
||
|
||
class ShapeLayers(models.Model):
|
||
name = models.CharField(_("Shape Layer"), max_length=255)
|
||
file = models.FileField(upload_to=get_file_path, blank=True)
|
||
uploaded_date = models.DateField(auto_now_add=True)
|
||
layerof = models.IntegerField(choices=LAYER_CHOICES, default=1)
|
||
table_name = models.CharField(_("Table name"), max_length=255, blank=True)
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
class ShapeFileLocations(models.Model):
|
||
shapefile=models.CharField(_('Shapelayer'), max_length=2048 ,blank=True, null=True)
|
||
locid=models.IntegerField(blank=True, null=True)
|
||
|
||
|
||
@receiver(pre_save, sender=Location)
|
||
def location_presave(sender, instance, *args, **kwargs):
|
||
print("------############------------", instance.location_id)
|
||
templocation.objects.filter(location_id = instance.location_id).delete()
|
||
|
||
|
||
@receiver(pre_save, sender=Location_line)
|
||
def location_presave(sender, instance, *args, **kwargs):
|
||
Location_line.objects.filter(location_id = instance.location_id).delete()
|
||
|
||
@receiver(pre_save, sender=Location_polygon)
|
||
def location_presave(sender, instance, *args, **kwargs):
|
||
Location_polygon.objects.filter(location_id = instance.location_id).delete()
|
||
|
||
|
||
|
||
@receiver(pre_save, sender=ShapeLayers)
|
||
def my_callback(sender, instance, *args, **kwargs):
|
||
|
||
instance.table_name = getTableForModel(instance.layerof)
|
||
|
||
|
||
def deletePrevious(mdl, fields):
|
||
with transaction.atomic():
|
||
mdl.objects.filter(location_id = int(fields[0])).delete();
|
||
|
||
|
||
@receiver(post_delete,sender=ShapeLayers)
|
||
def deleteShapelocation(sender,instance,*args,**kwargs):
|
||
locids = ShapeFileLocations.objects.filter(shapefile=instance.name).values_list('locid')
|
||
print("------- name----")
|
||
print(locids)
|
||
print("------- name----")
|
||
templocation.objects.all().delete()
|
||
ShapeFileLocations.objects.filter(shapefile=instance.name).delete()
|
||
|
||
|
||
|
||
@receiver(post_save, sender=ShapeLayers)
|
||
def publish_data(sender, instance, created, **kwargs):
|
||
logger.info(f"Processing ShapeLayer: {instance.name}")
|
||
|
||
file = instance.file.path
|
||
file_format = os.path.basename(file).split('.')[-1]
|
||
file_name = os.path.basename(file).split('.')[0]
|
||
file_path = os.path.dirname(file)
|
||
name = instance.name
|
||
conn_str = f'postgresql://{env("POSTGRES_USER")}:{env("POSTGRES_PASS")}@{env("PG_HOST")}:{env("PG_PORT")}/{env("POSTGRES_DBNAME")}'
|
||
|
||
with zipfile.ZipFile(file, 'r') as zip_ref:
|
||
zip_ref.extractall(file_path)
|
||
|
||
os.remove(file)
|
||
|
||
try:
|
||
#logger.debug("Attempting to read shape file")
|
||
|
||
# print("before reading the file")
|
||
shp = glob.glob(r'{}/**/*.shp'.format(file_path), recursive=True)[0]
|
||
|
||
#logger.info(f"Shape file read: {shp}")
|
||
|
||
# print("this is the read file",shp)
|
||
gdf = gpd.read_file(shp)
|
||
crs_name = str(gdf.crs.srs)
|
||
#logger.debug(f"CRS name: {crs_name}")
|
||
|
||
# print(crs_name, 'crs - name')
|
||
epsg = int(crs_name.replace('epsg:',''))
|
||
if epsg is None:
|
||
epsg=4326
|
||
|
||
lm2 = getTempMappingforModel(instance.layerof, shp)
|
||
#logger.info("Saving to temporary table")
|
||
|
||
# print("### shape file is ###")
|
||
lm2.save(strict=True, verbose=True)
|
||
#logger.info("Save to temporary table completed")
|
||
|
||
os.remove(shp)
|
||
except Exception as e:
|
||
print('######## shape file##########',e)
|
||
|
||
try:
|
||
logger.debug("Attempting to read CSV file")
|
||
|
||
csv_f = glob.glob(r'{}/**/*.csv'.format(file_path), recursive=True)[0]
|
||
|
||
remove_bom_inplace(csv_f)
|
||
|
||
mdl = apps.get_model(app_label="rog", model_name=LAYER_CHOICES[instance.layerof -1][1])
|
||
print(mdl)
|
||
print(f"#### instance.layerof - {instance.layerof}")
|
||
#logger.debug(f"Model for layer: {mdl}")
|
||
|
||
with open(csv_f, mode="r", encoding="utf-8") as txt_file:
|
||
#heading = next(txt_file)
|
||
reader = csv.reader(txt_file, delimiter=",")
|
||
for fields in reader:
|
||
logger.debug(f"Processing row: {fields[0]}")
|
||
print("@@@@@@@@@@@@")
|
||
print(fields[0])
|
||
print("@@@@@@@@@@@@")
|
||
if instance.layerof == 1:
|
||
#insertShapeLayerLocation(instance.name, fields)
|
||
#updateLocation(mdl, fields)
|
||
update_or_create_location(mdl, fields)
|
||
if instance.layerof == 2:
|
||
updateLineTable(mdl, fields)
|
||
if instance.layerof == 3:
|
||
updatePolygonTable(mdl, fields)
|
||
|
||
with open(csv_f, mode="r", encoding="utf-8") as txt_file:
|
||
reader_2 = csv.reader(txt_file, delimiter=",")
|
||
for fields in reader_2:
|
||
logger.debug(f"Inserting ShapeLayerLocation: {fields[0]}")
|
||
print("@@@@@@@@@@@@")
|
||
print(fields[0])
|
||
print("@@@@@@@@@@@@")
|
||
if instance.layerof == 1:
|
||
insertShapeLayerLocation(instance.name, fields)
|
||
|
||
logger.info("CSV processing completed")
|
||
|
||
except Exception as e:
|
||
print('######## csv file ##########',e)
|
||
|
||
|
||
def insertShapeLayerLocation(name, fields):
|
||
logger.info(f"Attempting to insert ShapeFileLocations for file: {name}, location_id: {fields[0]}")
|
||
try:
|
||
sll = UserUploadUser(userfile=name, email=fields[0])
|
||
sll.save();
|
||
except Exception as e:
|
||
logger.error(f"Error inserting ShapeFileLocations: {e}", exc_info=True)
|
||
|
||
|
||
def insertUserUploadUser(name, fields):
|
||
try:
|
||
with transaction.atomic():
|
||
sll = UserUploadUser(userfile=name, email=fields[0])
|
||
sll.save()
|
||
except Exception as e:
|
||
logger.error(f"Error updating TempLocation: {e}", exc_info=True)
|
||
|
||
def update_or_create_location(mdl, fields):
|
||
try:
|
||
with transaction.atomic():
|
||
latitude = float(fields[11]) if fields[11] and len(fields[11]) > 0 else None
|
||
longitude = float(fields[12]) if fields[12] and len(fields[12]) > 0 else None
|
||
|
||
geom = MultiPoint(Point(longitude, latitude)) if latitude is not None and longitude is not None else None
|
||
|
||
|
||
defaults={
|
||
'sub_loc_id': fields[1] if len(fields[1]) > 0 else '',
|
||
'cp': fields[2] if len(fields[2]) > 0 else 0,
|
||
# その他のフィールド...
|
||
'location_name': fields[3] if len(fields[3]) > 0 else '',
|
||
'category': fields[4] if len(fields[4]) > 0 else '',
|
||
'subcategory': fields[5] if len(fields[5]) > 0 else '',
|
||
'zip': fields[6] if len(fields[6]) > 0 else '',
|
||
'address': fields[7] if len(fields[7]) > 0 else '',
|
||
'prefecture': fields[8] if len(fields[8]) > 0 else '',
|
||
'area': fields[9] if len(fields[9]) > 0 else '',
|
||
'city': fields[10] if len(fields[10]) > 0 else '',
|
||
'latitude': latitude,
|
||
'longitude': longitude,
|
||
'photos': fields[13] if len(fields[13]) > 0 else '',
|
||
'videos': fields[14] if len(fields[14]) > 0 else '',
|
||
'webcontents': fields[15] if len(fields[15]) > 0 else '',
|
||
'status': fields[16] if len(fields[16]) > 0 else '',
|
||
'portal': fields[17] if len(fields[17]) > 0 else '',
|
||
'group': fields[18] if len(fields[18]) > 0 else '',
|
||
'phone': fields[19] if len(fields[19]) > 0 else '',
|
||
'fax': fields[20] if len(fields[20]) > 0 else '',
|
||
'email': fields[21] if len(fields[21]) > 0 else '',
|
||
'facility': fields[22] if len(fields[22]) > 0 else '',
|
||
'remark': fields[23] if len(fields[23]) > 0 else '',
|
||
'tags': fields[24] if len(fields[24]) > 0 else '',
|
||
'hidden_location': fields[25] if len(fields[25]) > 0 else False,
|
||
'auto_checkin': fields[26] if len(fields[26]) > 0 else False,
|
||
'checkin_radius': fields[27] if len(fields[27]) > 0 else 15,
|
||
'checkin_point': fields[28] if len(fields[28]) > 0 else 10,
|
||
'buy_point': fields[29] if len(fields[29]) > 0 else 0,
|
||
'evaluation_value': fields[30] if len(fields[30]) > 0 else '',
|
||
'shop_closed': fields[31] if len(fields[31]) > 0 else False,
|
||
'shop_shutdown': fields[32] if len(fields[32]) > 0 else False,
|
||
'opening_hours_mon': fields[33] if len(fields[33]) > 0 else '',
|
||
'opening_hours_tue': fields[34] if len(fields[34]) > 0 else '',
|
||
'opening_hours_wed': fields[35] if len(fields[35]) > 0 else '',
|
||
'opening_hours_thu': fields[36] if len(fields[36]) > 0 else '',
|
||
'opening_hours_fri': fields[37] if len(fields[37]) > 0 else '',
|
||
'opening_hours_sat': fields[38] if len(fields[38]) > 0 else '',
|
||
'opening_hours_sun': fields[39] if len(fields[39]) > 0 else ''
|
||
}
|
||
if geom:
|
||
defaults['geom'] = geom
|
||
|
||
obj, created = mdl.objects.update_or_create(
|
||
location_id=int(fields[0]),
|
||
defaults=defaults
|
||
)
|
||
|
||
if created:
|
||
logger.info(f"New location created with id: {obj.location_id}")
|
||
else:
|
||
logger.info(f"Location updated with id: {obj.location_id}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error updating or creating location: {e}", exc_info=True)
|
||
|
||
|
||
def updateLocation(mdl, fields):
|
||
print(f"Updating {fields[0]} - {fields[1]}")
|
||
print(mdl.objects.filter(location_id = int(fields[0])))
|
||
print(f"--- ${fields} ----")
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
updated = mdl.objects.filter(location_id = int(fields[0])).update(
|
||
sub_loc_id = fields[1] if len(fields[1]) > 0 else '',
|
||
cp = fields[2] if len(fields[2]) > 0 else 0,
|
||
location_name = fields[3] if len(fields[3]) > 0 else '',
|
||
category = fields[4] if len(fields[4]) > 0 else '',
|
||
subcategory = fields[5] if len(fields[5]) > 0 else '',
|
||
zip = fields[6] if len(fields[6]) > 0 else '',
|
||
address = fields[7] if len(fields[7]) > 0 else '',
|
||
prefecture = fields[8] if len(fields[8]) > 0 else '',
|
||
area = fields[9] if len(fields[9]) > 0 else '',
|
||
city = fields[10] if len(fields[10]) > 0 else '',
|
||
latitude = fields[11] if len(fields[11]) > 0 else '',
|
||
longitude = fields[12] if len(fields[12]) > 0 else '',
|
||
photos = fields[13] if len(fields[13]) > 0 else '',
|
||
videos = fields[14] if len(fields[14]) > 0 else '',
|
||
webcontents = fields[15] if len(fields[15]) > 0 else '',
|
||
status = fields[16] if len(fields[16]) > 0 else '',
|
||
portal = fields[17] if len(fields[17]) > 0 else '',
|
||
group = fields[18] if len(fields[18]) > 0 else '',
|
||
phone = fields[19] if len(fields[19]) > 0 else '',
|
||
fax = fields[20] if len(fields[20]) > 0 else '',
|
||
email = fields[21] if len(fields[21]) > 0 else '',
|
||
facility = fields[22] if len(fields[22]) > 0 else '',
|
||
remark = fields[23] if len(fields[23]) > 0 else '',
|
||
tags = fields[24] if len(fields[24]) > 0 else '',
|
||
hidden_location = fields[25] if len(fields[25]) > 0 else False,
|
||
auto_checkin = fields[26] if len(fields[26]) > 0 else False,
|
||
checkin_radius = fields[27] if len(fields[27]) > 0 else 15,
|
||
checkin_point = fields[28] if len(fields[28]) > 0 else 10,
|
||
buy_point = fields[29] if len(fields[29]) > 0 else 0,
|
||
evaluation_value = fields[30] if len(fields[30]) > 0 else '',
|
||
shop_closed = fields[31] if len(fields[31]) > 0 else False,
|
||
shop_shutdown = fields[32] if len(fields[32]) > 0 else False,
|
||
opening_hours_mon = fields[33] if len(fields[33]) > 0 else '',
|
||
opening_hours_tue = fields[34] if len(fields[34]) > 0 else '',
|
||
opening_hours_wed = fields[35] if len(fields[35]) > 0 else '',
|
||
opening_hours_thu = fields[36] if len(fields[36]) > 0 else '',
|
||
opening_hours_fri = fields[37] if len(fields[37]) > 0 else '',
|
||
opening_hours_sat = fields[38] if len(fields[38]) > 0 else '',
|
||
opening_hours_sun = fields[39] if len(fields[39]) > 0 else ''
|
||
)
|
||
logger.info(f"TempLocation updated successfully. Rows affected: {updated}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error updating TempLocation: {e}", exc_info=True)
|
||
|
||
|
||
def updateLineTable(mdl, fields):
|
||
print(f"Updating {fields[0]} - {fields[1]}")
|
||
print(mdl.objects.filter(location_id = int(fields[0])))
|
||
print("-------")
|
||
with transaction.atomic():
|
||
mdl.objects.filter(location_id = int(fields[0])).update(
|
||
location_name= fields[1] if len(fields) > 1 else '',
|
||
category=fields[2] if len(fields) > 2 else '',
|
||
zip=fields[3] if len(fields) > 3 else '',
|
||
address=fields[4] if len(fields) > 4 else '',
|
||
prefecture=fields[5] if len(fields) > 5 else '',
|
||
area=fields[6] if len(fields) > 6 else '',
|
||
city=fields[7] if len(fields) > 7 else '',
|
||
photos=fields[8] if len(fields) > 8 else '',
|
||
videos=fields[9] if len(fields) > 9 else '',
|
||
webcontents=fields[10] if len(fields) > 10 else '',
|
||
status=fields[11] if len(fields) > 11 else '',
|
||
portal=fields[12] if len(fields) > 12 else '',
|
||
group=fields[13] if len(fields) > 13 else '',
|
||
phone=fields[14] if len(fields) > 14 else '',
|
||
fax=fields[15] if len(fields) > 15 else '',
|
||
email=fields[16] if len(fields) > 16 else '',
|
||
facility=fields[17] if len(fields) > 17 else '',
|
||
remark=fields[18] if len(fields) > 18 else '',
|
||
tags=fields[19] if len(fields) > 19 else '',
|
||
parammeters=fields[20] if len(fields) > 20 else ''
|
||
)
|
||
|
||
|
||
def updatePolygonTable(mdl, fields):
|
||
#print(f"Updated {fields[0]} - {fields[1]}")
|
||
with transaction.atomic():
|
||
mdl.objects.filter(location_id = fields[0]).update(
|
||
location_name= fields[1] if len(fields) > 1 else '',
|
||
category=fields[2] if len(fields) > 2 else '',
|
||
zip=fields[3] if len(fields) > 3 else '',
|
||
address=fields[4] if len(fields) > 4 else '',
|
||
prefecture=fields[5] if len(fields) > 5 else '',
|
||
area=fields[6] if len(fields) > 6 else '',
|
||
city=fields[7] if len(fields) > 7 else '',
|
||
photos=fields[8] if len(fields) > 8 else '',
|
||
videos=fields[9] if len(fields) > 9 else '',
|
||
webcontents=fields[10] if len(fields) > 10 else '',
|
||
status=fields[11] if len(fields) > 11 else '',
|
||
portal=fields[12] if len(fields) > 12 else '',
|
||
group=fields[13] if len(fields) > 13 else '',
|
||
phone=fields[14] if len(fields) > 14 else '',
|
||
fax=fields[15] if len(fields) > 15 else '',
|
||
email=fields[16] if len(fields) > 16 else '',
|
||
facility=fields[17] if len(fields) > 17 else '',
|
||
remark=fields[18] if len(fields) > 18 else '',
|
||
tags=fields[19] if len(fields) > 19 else '',
|
||
parammeters=fields[20] if len(fields) > 20 else ''
|
||
)
|
||
|
||
|
||
def createUser(fields):
|
||
with transaction.atomic():
|
||
other_fields.setdefault('event_code',fields[1])
|
||
other_fields.setdefault('team_name',fields[1])
|
||
other_fields.setdefault('group',fields[1])
|
||
user = CustomUser.objects.create_user(email=fields[0], password=fields[4], **other_fields)
|
||
user.is_superuser = False
|
||
user.is_staff = False
|
||
user.save()
|
||
|
||
|
||
@receiver(post_delete,sender=UserUpload)
|
||
def deleteUserUploadUser(sender,instance,*args,**kwargs):
|
||
pass
|
||
emails = UserUploadUser.objects.filter(userfile=instance.name).values_list('email')
|
||
print("------- email----")
|
||
print(emails)
|
||
print("------- name----")
|
||
CustomUser.objects.filter(email__in=emails).delete()
|
||
templocation.objects.all().delete()
|
||
UserUploadUser.objects.filter(userfile=instance.name).delete();
|
||
|
||
|
||
@receiver(post_save, sender=UserUpload)
|
||
def publish_data(sender, instance, created, **kwargs):
|
||
logger.info(f"Processing ShapeLayer: {instance.name}")
|
||
|
||
file = instance.file.path
|
||
#os.remove(file)
|
||
|
||
try:
|
||
#csv_f = glob.glob(r'{}/**/*.csv'.format(file), recursive=True)[0]
|
||
|
||
remove_bom_inplace(file)
|
||
print(file)
|
||
with open(file, mode="r", encoding="utf-8") as txt_file:
|
||
#heading = next(txt_file)
|
||
reader = csv.reader(txt_file, delimiter=",")
|
||
for fields in reader:
|
||
print("@@@@@@@@@@@@")
|
||
# print(fields[0])
|
||
# print(fields[1])
|
||
# print(fields[2])
|
||
print("@@@@@@@@@@@@")
|
||
createUser(fields)
|
||
|
||
with open(file, mode="r", encoding="utf-8") as txt_file:
|
||
reader_2 = csv.reader(txt_file, delimiter=",")
|
||
for fields in reader_2:
|
||
insertUserUploadUser(instance.name, fields)
|
||
except Exception as e:
|
||
print('######## user csv file ##########',e)
|
||
|
||
|
||
# for upper compatible
|
||
|
||
# 既存のモデルに追加=> 通過記録に相応しい名称に変更すべき
|
||
|
||
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):
|
||
"""
|
||
GPSチェックイン情報を管理するモデル
|
||
gps_informationテーブルに対応
|
||
"""
|
||
serial_number = models.IntegerField(null=False)
|
||
|
||
# Entry へ移行
|
||
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)
|
||
|
||
# 新規追加
|
||
checkin_time = models.DateTimeField(auto_now_add=True)
|
||
|
||
goal_time = models.TextField(null=True, blank=True)
|
||
late_point = models.IntegerField(null=True, blank=True)
|
||
create_at = models.DateTimeField(null=True, blank=True)
|
||
create_user = models.TextField(null=True, blank=True)
|
||
update_at = models.DateTimeField(null=True, blank=True)
|
||
update_user = models.TextField(null=True, blank=True)
|
||
buy_flag = models.BooleanField(null=True, blank=True)
|
||
minus_photo_flag = models.BooleanField(null=True, blank=True)
|
||
colabo_company_memo = models.TextField(null=False, default='')
|
||
|
||
# 新規追加
|
||
is_service_checked = models.BooleanField(default=False)
|
||
|
||
# ゴール記録用に追加
|
||
score = models.IntegerField(default=0, null=True, blank=True)
|
||
scoreboard_url = models.URLField(blank=True, null=True)
|
||
|
||
class Meta:
|
||
db_table = 'gps_information'
|
||
# 複合主キーの設定
|
||
unique_together = [['serial_number', 'zekken_number', 'event_code', 'colabo_company_memo']]
|
||
# インデックスの設定(必要に応じて)
|
||
indexes = [
|
||
models.Index(fields=['zekken_number', 'event_code'], name='gpslog_zekken_event_idx'),
|
||
models.Index(fields=['create_at'], name='gpslog_create_at_idx'),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.event_code}-{self.zekken_number}-{self.serial_number}"
|
||
|
||
|
||
@classmethod
|
||
def record_start(cls, entry):
|
||
"""
|
||
チームのスタート情報を記録する
|
||
以前はTeamStartモデルが担当していた機能
|
||
"""
|
||
return cls.objects.create(
|
||
serial_number=0, # スタートログを表す特別な値
|
||
entry=entry,
|
||
zekken_number=entry.zekken_number,
|
||
event_code=entry.event.event_name,
|
||
cp_number="START",
|
||
create_at=timezone.now(),
|
||
update_at=timezone.now(),
|
||
buy_flag=False,
|
||
colabo_company_memo=""
|
||
)
|
||
|
||
@classmethod
|
||
def record_goal(cls, entry, goal_time=None, image_url=None, score=0, scoreboard_url=None):
|
||
"""
|
||
チームのゴール情報を記録する
|
||
以前はTeamGoalモデルが担当していた機能
|
||
"""
|
||
if goal_time is None:
|
||
goal_time = timezone.now()
|
||
|
||
return cls.objects.create(
|
||
serial_number=9999, # ゴールログを表す特別な値
|
||
entry=entry,
|
||
zekken_number=entry.zekken_number,
|
||
event_code=entry.event.event_name,
|
||
cp_number="GOAL",
|
||
image_address=image_url,
|
||
create_at=goal_time,
|
||
update_at=timezone.now(),
|
||
goal_time=goal_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||
buy_flag=False,
|
||
score=score,
|
||
scoreboard_url=scoreboard_url,
|
||
colabo_company_memo=""
|
||
)
|
||
|
||
def is_start_record(self):
|
||
"""このレコードがスタート記録かどうかを判定"""
|
||
return self.cp_number == "START" and self.serial_number == 0
|
||
|
||
def is_goal_record(self):
|
||
"""このレコードがゴール記録かどうかを判定"""
|
||
return self.cp_number == "GOAL" and self.serial_number == 9999
|
||
|
||
|
||
@property
|
||
def start_time(self):
|
||
"""スタート時刻を返す(TeamStartとの互換性のため)"""
|
||
return self.create_at or self.checkin_time
|
||
|
||
@property
|
||
def goal_datetime(self):
|
||
"""ゴール時刻をDateTimeとして返す(TeamGoalとの互換性のため)"""
|
||
if self.is_goal_record() and self.create_at:
|
||
return self.create_at
|
||
return None
|
||
|
||
|
||
|
||
class Waypoint(models.Model):
|
||
entry = models.ForeignKey('Entry', on_delete=models.CASCADE, related_name='waypoints')
|
||
latitude = models.FloatField()
|
||
longitude = models.FloatField()
|
||
altitude = models.FloatField(null=True, blank=True)
|
||
accuracy = models.FloatField(null=True, blank=True)
|
||
speed = models.FloatField(null=True, blank=True)
|
||
recorded_at = models.DateTimeField()
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
ordering = ['recorded_at']
|
||
indexes = [
|
||
models.Index(fields=['entry', 'recorded_at']),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.entry.team_name} - {self.recorded_at.strftime('%Y-%m-%d %H:%M:%S')}" |