Files
rogaining_srv/rog/models.py
2025-09-02 23:14:14 +09:00

2097 lines
96 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
event_code = models.CharField(max_length=255, null=False, default='')
zekken = models.CharField(max_length=20, null=True, blank=True)
serial_number = models.CharField(max_length=20, null=True, blank=True)
cp_number = models.CharField(max_length=20, null=True, blank=True)
lat = models.FloatField(null=True, blank=True)
lng = models.FloatField(null=True, blank=True)
checkin_time = models.DateTimeField(null=True, blank=True)
record_time = models.DateTimeField(null=True, blank=True)
location = models.PointField(srid=4326, null=True, blank=True)
mobserver_id = models.IntegerField(null=True, blank=True)
event_id = models.BigIntegerField(null=True, blank=True)
team_id = models.BigIntegerField(null=True, blank=True)
checkpoint_id = models.BigIntegerField(null=True, blank=True)
# データベースの実際のフィールドと一致させる
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'rog_gpscheckin'
managed = True
def __str__(self):
return f"GPS Checkin {self.id} - {self.zekken}"
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)
category = models.CharField(_('カテゴリ'), max_length=255, blank=True, null=True)
sub_loc_id = models.CharField(_('サブロケーションID'), max_length=2048, blank=True, null=True)
subcategory = models.CharField(_('サブカテゴリ'), max_length=2048, blank=True, null=True)
# 位置情報
latitude = models.FloatField(_('緯度'), null=True, blank=True)
longitude = models.FloatField(_('経度'), null=True, blank=True)
location = models.PointField(_('位置'), srid=4326, null=True, blank=True)
# ポイント情報
checkin_point = models.IntegerField(_('チェックイン得点'), default=10)
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)
zip_code = models.CharField(_('郵便番号'), max_length=12, blank=True, null=True)
prefecture = models.CharField(_('都道府県'), max_length=255, blank=True, null=True)
area = models.CharField(_('地域'), max_length=255, blank=True, null=True)
city = models.CharField(_('市区町村'), max_length=255, blank=True, null=True)
phone = models.CharField(_('電話番号'), max_length=32, blank=True, null=True)
website = models.URLField(_('ウェブサイト'), blank=True, null=True)
facility = models.CharField(_('設備'), max_length=255, blank=True, null=True)
description = models.TextField(_('説明'), blank=True, null=True)
# 追加フィールドLocationテーブルから移行
photos = models.CharField(_('写真'), max_length=2048, blank=True, null=True)
videos = models.CharField(_('動画'), max_length=2048, blank=True, null=True)
remark = models.TextField(_('備考'), blank=True, null=True)
tags = models.CharField(_('タグ'), max_length=2048, blank=True, null=True)
evaluation_value = models.CharField(_('評価値'), max_length=255, blank=True, null=True)
hidden_location = models.BooleanField(_('隠しロケーション'), default=False)
# 管理情報
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.checkin_point + self.buy_point
@classmethod
def import_from_csv(cls, csv_file, event, user=None):
"""
CSVファイルからチェックポイントデータをインポート全フィールド対応
CSV形式:
cp_number,cp_name,latitude,longitude,checkin_point,buy_point,address,phone,description,
sub_loc_id,subcategory,photos,videos,tags,evaluation_value,remark,hidden_location
"""
import csv
import io
from django.utils import timezone
from django.contrib.gis.geos import Point
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列から番号を抽出 (例: "#1(5)" -> 1, "-2" -> -2)
cp_raw = row.get('cp', row.get('cp_number', ''))
cp_number = None
if cp_raw:
import re
# #で始まる場合は#の後の数字を抽出 (例: "#1(5)" -> "1")
if cp_raw.startswith('#'):
match = re.search(r'#(-?\d+)', str(cp_raw))
if match:
cp_number = int(match.group(1))
else:
# 直接数値の場合
try:
cp_number = int(cp_raw)
except ValueError:
pass
if cp_number is None:
errors.append(f"{row_num}: CP番号が無効です (値: {cp_raw})")
continue
# 緯度経度から位置情報を作成
latitude = float(row['latitude']) if row.get('latitude') else None
longitude = float(row['longitude']) if row.get('longitude') else None
location = None
if latitude and longitude:
location = Point(longitude, latitude)
# hidden_locationのブール値変換
hidden_location = False
if row.get('hidden_location'):
hidden_str = row.get('hidden_location', '').lower()
hidden_location = hidden_str in ['true', '1', 'yes', 'on']
# checkin_radiusの処理
checkin_radius = None
if row.get('checkin_radius'):
radius_str = row.get('checkin_radius', '').strip()
if radius_str and radius_str.lower() not in ['false', '', 'null']:
try:
checkin_radius = float(radius_str)
except ValueError:
# デフォルト値を設定
checkin_radius = -1.0 # 要タップ
else:
checkin_radius = -1.0 # CSVでFALSEや空の場合は要タップ
else:
checkin_radius = -1.0 # デフォルトは要タップ
defaults = {
# 基本フィールド
'cp_name': row.get('loc_name', row.get('cp_name', f'CP{cp_number}')),
'latitude': latitude,
'longitude': longitude,
'location': location,
'checkin_point': int(row.get('checkin_point', row.get('cp_point', 10))), # 後方互換性のためcp_pointもサポート
'buy_point': int(row.get('buy_point', 0)),
'address': row.get('address', ''),
'phone': row.get('phone', ''),
'description': row.get('description', row.get('remark', '')),
'checkin_radius': checkin_radius, # チェックイン範囲を追加
# 新しいフィールド
'category': row.get('category', ''),
'sub_loc_id': row.get('sub_loc_id', ''),
'subcategory': row.get('subcategory', ''),
'photos': row.get('photos', ''),
'videos': row.get('videos', ''),
'tags': row.get('tags', ''),
'evaluation_value': row.get('evaluation_value', ''),
'remark': row.get('remark', ''),
'hidden_location': hidden_location,
# 追加フィールド
'area': row.get('area', ''),
'zip_code': row.get('zip', ''),
'prefecture': row.get('prefecture', ''),
'city': row.get('city', ''),
'website': row.get('webcontent', ''),
'facility': row.get('facility', ''),
# 管理フィールド
'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, # スタートログを表す特別な値
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')}"