Files
rogaining_srv/rog/serializers.py
2025-09-02 11:40:17 +09:00

1347 lines
53 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, check_password
from django.contrib.auth import get_user_model
User = get_user_model()
import uuid
from django.db import IntegrityError
from django.conf import settings
from django.urls import reverse
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db import transaction
from rest_framework import serializers
from rest_framework_gis.serializers import GeoFeatureModelSerializer
from sqlalchemy.sql.functions import mode
from .models import Location2025, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser,NewEvent,NewEvent2, Team, NewCategory, Category, Entry, Member, TempUser,EntryMember, AppVersion, UploadedImage
from drf_extra_fields.fields import Base64ImageField
#from django.contrib.auth.models import User
from .models import CustomUser
from django.contrib.auth import authenticate
from .models import TestModel
import logging
from django.shortcuts import get_object_or_404
from django.utils import timezone
from datetime import datetime, date
logger = logging.getLogger(__name__)
class LocationCatSerializer(serializers.ModelSerializer):
class Meta:
model=Location2025
fields=['category',]
class LocationSerializer(serializers.ModelSerializer):
# evaluation_valueに基づくインタラクション情報を追加
interaction_type = serializers.SerializerMethodField()
requires_photo = serializers.SerializerMethodField()
requires_qr_code = serializers.SerializerMethodField()
interaction_instructions = serializers.SerializerMethodField()
# 追加フィールドのカスタムシリアライズ
has_photos = serializers.SerializerMethodField()
has_videos = serializers.SerializerMethodField()
photos_list = serializers.SerializerMethodField()
videos_list = serializers.SerializerMethodField()
tags_list = serializers.SerializerMethodField()
# 位置情報の緯度経度
latitude_float = serializers.SerializerMethodField()
longitude_float = serializers.SerializerMethodField()
# フィールド名の別名対応
webcontent = serializers.SerializerMethodField()
zip = serializers.SerializerMethodField()
s3_name = serializers.SerializerMethodField()
def get_webcontent(self, obj):
"""websiteフィールドをwebcontentとして返す"""
return obj.website
def get_zip(self, obj):
"""zip_codeフィールドをzipとして返す"""
return obj.zip_code
def get_s3_name(self, obj):
"""S3ファイル名を生成して返すevent_nameのローマ字表記"""
if not obj.event:
return None
# NewEvent2のs3_nameフィールドを参照
if hasattr(obj.event, 's3_name') and obj.event.s3_name:
return obj.event.s3_name
# フォールバック: event_nameをローマ字に変換
if obj.event.event_name:
s3_name = self._convert_japanese_to_romaji(obj.event.event_name)
return s3_name
# 最終フォールバック: イベントコード
return obj.event.event_code
def _convert_japanese_to_romaji(self, japanese_text):
"""日本語をローマ字に変換する"""
# 基本的な日本語→ローマ字変換テーブル
conversion_table = {
# 地名
'岐阜': 'gifu',
'大垣': 'ogaki',
'多治見': 'tajimi',
'': 'seki',
'中津川': 'nakatsugawa',
'美濃': 'mino',
'瑞浪': 'mizunami',
'羽島': 'hashima',
'恵那': 'ena',
'美濃加茂': 'minokamo',
'土岐': 'toki',
'各務原': 'kakamigahara',
'可児': 'kani',
'山県': 'yamagata',
'瑞穂': 'mizuho',
'飛騨': 'hida',
'本巣': 'motosu',
'郡上': 'gujo',
'下呂': 'gero',
'海津': 'kaizu',
# 一般的な単語
'ロゲイニング': 'rogaining',
'ロゲ': 'roge',
'イベント': 'event',
'大会': 'taikai',
'競技': 'kyogi',
'スポーツ': 'sports',
'アウトドア': 'outdoor',
'ハイキング': 'hiking',
'ウォーキング': 'walking',
'ランニング': 'running',
'マラソン': 'marathon',
'トレイル': 'trail',
'オリエンテーリング': 'orienteering',
'ナビゲーション': 'navigation',
'チェックポイント': 'checkpoint',
'ゴール': 'goal',
'スタート': 'start',
'チーム': 'team',
'ファミリー': 'family',
'ジュニア': 'junior',
'シニア': 'senior',
'初心者': 'beginner',
'上級者': 'advanced',
'中級者': 'intermediate',
# 年号
'': '2025',
'2025': '2025',
'': '2024',
'2024': '2024',
'': '2026',
'2026': '2026',
'': '2027',
'2027': '2027',
'': '2028',
'2028': '2028',
'': '2029',
'2029': '2029',
'': '2030',
'2030': '2030',
# 月
'1月': 'january',
'2月': 'february',
'3月': 'march',
'4月': 'april',
'5月': 'may',
'6月': 'june',
'7月': 'july',
'8月': 'august',
'9月': 'september',
'10月': 'october',
'11月': 'november',
'12月': 'december',
# その他
'': 'spring',
'': 'summer',
'': 'autumn',
'': 'winter',
'': 'dai',
'': 'kai',
'': 'hai',
'記念': 'kinen',
'特別': 'tokubetsu',
'公式': 'koshiki',
'練習': 'renshu',
'体験': 'taiken'
}
result = japanese_text.lower()
# 変換テーブルを使用して変換
for japanese, romaji in conversion_table.items():
result = result.replace(japanese, romaji)
# 記号の変換
result = result.replace(' ', '_')
result = result.replace('-', '_')
result = result.replace(' ', '_') # 全角スペース
result = result.replace('', '_')
result = result.replace('/', '_')
result = result.replace('\\', '_')
result = result.replace('(', '_')
result = result.replace(')', '_')
result = result.replace('', '_')
result = result.replace('', '_')
result = result.replace('[', '_')
result = result.replace(']', '_')
result = result.replace('', '_')
result = result.replace('', '_')
result = result.replace(':', '_')
result = result.replace('', '_')
result = result.replace(';', '_')
result = result.replace('', '_')
result = result.replace(',', '_')
result = result.replace('', '_')
result = result.replace('.', '_')
result = result.replace('', '_')
result = result.replace('!', '_')
result = result.replace('', '_')
result = result.replace('?', '_')
result = result.replace('', '_')
# 連続するアンダースコアを単一に
while '__' in result:
result = result.replace('__', '_')
# 先頭と末尾のアンダースコアを削除
result = result.strip('_')
# 空文字列の場合は'event'を返す
if not result:
result = 'event'
return result
class Meta:
model=Location2025
fields=[
# 基本フィールド
'id', 'cp_number', 'event', 'cp_name', 's3_name','category', 'sub_loc_id', 'subcategory',
'latitude', 'longitude', 'location', 'address',
'checkin_point', 'buy_point',
'checkin_radius', 'auto_checkin',
'shop_closed', 'shop_shutdown', 'opening_hours',
'phone', 'website', 'facility', 'description', 'remark',
# 住所詳細フィールド
'zip_code', 'prefecture', 'area', 'city',
# 追加フィールド
'photos', 'videos', 'tags', 'evaluation_value', 'hidden_location',
# 管理フィールド
'is_active', 'sort_order', 'csv_source_file', 'csv_upload_date', 'csv_upload_user',
'created_at', 'updated_at', 'created_by', 'updated_by',
# カスタムフィールド
'interaction_type', 'requires_photo', 'requires_qr_code', 'interaction_instructions',
'has_photos', 'has_videos', 'photos_list', 'videos_list', 'tags_list',
'latitude_float', 'longitude_float', 'webcontent', 'zip'
]
def get_latitude_float(self, obj):
"""位置情報から緯度を取得"""
if obj.location:
return obj.location.y
return obj.latitude
def get_longitude_float(self, obj):
"""位置情報から経度を取得"""
if obj.location:
return obj.location.x
return obj.longitude
def get_has_photos(self, obj):
"""写真データの有無を返す"""
return bool(obj.photos and obj.photos.strip())
def get_has_videos(self, obj):
"""動画データの有無を返す"""
return bool(obj.videos and obj.videos.strip())
def get_photos_list(self, obj):
"""写真ファイル名をリストで返す"""
if not obj.photos or not obj.photos.strip():
return []
# カンマ区切りで分割してリストとして返す
return [photo.strip() for photo in obj.photos.split(',') if photo.strip()]
def get_videos_list(self, obj):
"""動画ファイル名をリストで返す"""
if not obj.videos or not obj.videos.strip():
return []
# カンマ区切りで分割してリストとして返す
return [video.strip() for video in obj.videos.split(',') if video.strip()]
def get_tags_list(self, obj):
"""タグをリストで返す"""
if not obj.tags or not obj.tags.strip():
return []
# カンマ区切りで分割してリストとして返す
return [tag.strip() for tag in obj.tags.split(',') if tag.strip()]
def get_interaction_type(self, obj):
"""evaluation_valueに基づくインタラクションタイプを返す"""
try:
from .location_interaction import get_interaction_type
return get_interaction_type(obj)['type']
except ImportError:
return obj.evaluation_value or "0"
def get_requires_photo(self, obj):
"""写真撮影が必要かどうかを返す"""
try:
from .location_interaction import get_interaction_type
return get_interaction_type(obj)['requires_photo']
except ImportError:
return obj.evaluation_value == "1"
def get_requires_qr_code(self, obj):
"""QRコードスキャンが必要かどうかを返す"""
try:
from .location_interaction import should_use_qr_code
return should_use_qr_code(obj)
except ImportError:
return obj.evaluation_value == "2" or getattr(obj, 'use_qr_code', False)
def get_interaction_instructions(self, obj):
"""インタラクション手順を返す"""
try:
from .location_interaction import get_interaction_type
return get_interaction_type(obj)['instructions']
except ImportError:
evaluation_value = obj.evaluation_value or "0"
if evaluation_value == "1":
return "商品の写真を撮影してください。買い物をすることでポイントを獲得できます"
elif evaluation_value == "2":
return "QRコードをスキャンしてください"
else:
return "この場所でチェックインしてポイントを獲得してください"
class Location_lineSerializer(GeoFeatureModelSerializer):
class Meta:
model=Location_line
geo_field='geom'
fields="__all__"
class Location_polygonSerializer(GeoFeatureModelSerializer):
class Meta:
model=Location_polygon
geo_field='geom'
fields="__all__"
class JPN_main_perfSerializer(serializers.ModelSerializer):
class Meta:
model=JpnAdminMainPerf
fields=['id', 'adm0_en', 'adm0_ja', 'adm0_pcode', 'adm1_en', 'adm1_ja', 'adm1_pcode']
# class JPN_sub_perSerializer(serializers.ModelSerializer):
# class Meta:
# model=JpnSubPerf
# fields=['id', 'adm0_en', 'adm0_ja', 'adm0_pcode', 'adm1_en', 'adm1_ja', 'adm1_pcode', 'adm2_ja', 'adm2_en', 'adm2_pcode']
# class JPN_perfSerializer(serializers.ModelSerializer):
# class Meta:
# model=JpnAdminPerf
# fields=['id','et_id', 'et_right', 'et_left', 'adm2_l', 'adm1_l', 'adm0_l', 'adm0_r', 'adm1_r', 'adm2_r', 'admlevel']
class GifuAreaSerializer(serializers.ModelSerializer):
class Meta:
model= GifuAreas
fields=['id', 'adm0_ja', 'adm0_pcode', 'adm1_en', 'adm1_ja', 'adm1_pcode', 'adm2_ja', 'adm2_en', 'adm2_pcode', 'area_nm']
class UserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True, validators=[validate_password])
class Meta:
model = CustomUser
fields = ('email', 'password', 'password2', 'firstname', 'lastname', 'date_of_birth', 'female')
extra_kwargs = {
'email': {'required': True},
'firstname': {'required': True},
'lastname': {'required': True},
'date_of_birth': {'required': True},
}
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
try:
validate_password(attrs['password'])
except ValidationError as e:
raise serializers.ValidationError({"password": list(e.messages)})
return attrs
def validate_email(self, value):
if CustomUser.objects.filter(email=value).exists() or TempUser.objects.filter(email=value).exists():
raise serializers.ValidationError("この電子メールアドレスは既に使用されています。")
return value
def create(self, validated_data):
raw_password = validated_data.get('password')
# デバッグコード
hashed_password = make_password(raw_password)
print(f"Hashed password during registration: {hashed_password}")
is_valid = check_password(raw_password, hashed_password)
print(f"Password is valid during registration: {is_valid}")
validated_data['password'] = hashed_password
return super(UserRegistrationSerializer, self).create(validated_data)
#validated_data['password'] = make_password(validated_data.get('password'))
#return super(UserRegistrationSerializer, self).create(validated_data)
# try:
# with transaction.atomic():
# password = validated_data['password'].encode('utf-8').decode('utf-8')
#
# user = CustomUser.objects.create_user(
# email=validated_data['email'],
# password=password, # validated_data['password'],
# firstname=validated_data['firstname'],
# lastname=validated_data['lastname'],
# date_of_birth=validated_data['date_of_birth'],
# female=validated_data.get('female', False),
# group='' # この値は必要に応じて変更してください
# )
# logger.debug(f"Creating user with data: {validated_data}")
# user.set_password(validated_data['password'])
# user.save()
#
# return user
# except ValidationError as e:
# raise serializers.ValidationError({"password": list(e.messages)})
#class CreateUserSerializer(serializers.ModelSerializer):
# class Meta:
# model = CustomUser
# fields = ('email', 'password')
# extra_kwargs = {'password': {'write_only': True}}
#
# def create(self, validated_data):
# user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','')
# return user
class TempUserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = TempUser
fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female')
def create(self, validated_data):
# パスワードのハッシュ化はviewで行うので、ここではそのまま保存
return TempUser.objects.create(**validated_data)
#validated_data['verification_code'] = str(uuid.uuid4())
#raw_password = validated_data.get('password')
#hashed_password = make_password(raw_password)
#validated_data['password'] = hashed_password
#return TempUser.objects.create(**validated_data)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ('id','email', 'is_rogaining' ,'group', 'zekken_number', 'event_code', 'team_name', 'is_staff')
class GolaImageSerializer(serializers.ModelSerializer):
goalimage = Base64ImageField(max_length=None, use_url=True)
class Meta:
model = GoalImages
fields="__all__"
def get_goalimage_url_old(self, car):
request = self.context.get('request')
photo_url = GoalImages.goalimage.url
return request.build_absolute_uri(photo_url)
def get_goalimage_url(self, obj):
request = self.context.get('request')
if request is None:
logger.warning("Request not found in serializer context")
return None
try:
photo_url = obj.goalimage.url
absolute_url = request.build_absolute_uri(photo_url)
logger.info(f"Generated URL for goalimage: {absolute_url}")
return absolute_url
except AttributeError as e:
logger.error(f"Error generating URL for goalimage: {str(e)}")
return None
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['goalimage_url'] = self.get_goalimage_url(instance)
logger.debug(f"Serialized data: {representation}")
return representation
class CheckinImageSerializer(serializers.ModelSerializer):
checkinimage = Base64ImageField(max_length=None, use_url=True)
class Meta:
model = CheckinImages
fields="__all__"
def get_checkinimage_url(self, car):
request = self.context.get('request')
photo_url = CheckinImages.checkinimage.url
return request.build_absolute_uri(photo_url)
class RogUserSerializer(serializers.ModelSerializer):
class Meta:
model = RogUser
fields = ('id','user', 'paid',)
class LoginUserSerializer(serializers.Serializer):
#email = serializers.CharField()
email = serializers.EmailField()
password = serializers.CharField()
def validate(self, data):
email = data.get('email')
password = data.get('password')
if email and password:
user = authenticate(username=email, password=password)
if user:
if user.is_active:
return user
raise serializers.ValidationError("User account is disabled.")
else:
# Check if the user exists
try:
user_obj = User.objects.get(email=email)
raise serializers.ValidationError("Incorrect password.")
except User.DoesNotExist:
raise serializers.ValidationError("User with this email does not exist.")
else:
raise serializers.ValidationError("Must include 'email' and 'password'.")
class UseractionsSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
#location = LocationSerializer(read_only=True)
location = serializers.RelatedField(source='Location', read_only=True)
#location = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Useractions
fields = ('id', 'user', 'location', 'wanttogo', 'like', 'checkin', 'order',)
class UserDestinationSerializer(serializers.ModelSerializer):
#user = UserSerializer(read_only=True)
location = LocationSerializer(read_only=True)
#location = serializers.RelatedField(source='Location', read_only=True)
#location = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Useractions
fields = ('id', 'user', 'location', 'wanttogo', 'like', 'checkin')
class LocationEventNameSerializer(serializers.ModelSerializer):
class Meta:
model = Location2025
fields = ('id', 'event_name',)
class UserTracksSerializer(GeoFeatureModelSerializer):
user_id = serializers.IntegerField()
class Meta:
model=UserTracks
geo_field = 'geom'
fields = ["user_id",]
def create(self, validated_data):
user_id = validated_data.pop("user_id", None)
user = CustomUser.objects.get(id=user_id)
return UserTracks.objects.create(user=user, **validated_data)
class TestSerialiser(serializers.ModelSerializer):
class Meta:
model = TestModel
fields = ('id', 'testbane', 'wanttogo', 'like', 'checkin')
class ChangePasswordSerializer(serializers.Serializer):
model = CustomUser
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
class RegistrationSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(style={"input_type": "password"}, write_only=True)
class Meta:
model = CustomUser
fields = ['email', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'password', 'password2']
extra_kwargs = {
'password': {'write_only': True}
}
def save(self):
user = CustomUser(email=self.validated_data['email'], is_rogaining=self.validated_data['is_rogaining'], zekken_number=self.validated_data['zekken_number'], event_code=self.validated_data['event_code'], team_name=self.validated_data['team_name'], group=self.validated_data['group'])
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2:
raise serializers.ValidationError({'password': 'Passwords must match.'})
user.set_password(password)
user.save()
return user
class NewCategorySerializer(serializers.ModelSerializer):
class Meta:
model = NewCategory
fields = ['id','category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female']
#fields = ['id','category_name', 'category_number']
class NewEvent2Serializer(serializers.ModelSerializer):
# API変更要求書対応: deadline_datetime フィールド追加
deadline_datetime = serializers.DateTimeField(source='deadlineDateTime', read_only=True)
# Supervisor web app compatibility
code = serializers.CharField(source='event_name', read_only=True)
name = serializers.CharField(source='event_name', read_only=True)
class Meta:
model = NewEvent2
fields = [
'id', 'event_name', 'start_datetime', 'end_datetime',
'deadlineDateTime', 'deadline_datetime', 'status', 'public',
'hour_3', 'hour_5', 'class_general', 'class_family',
'class_solo_male', 'class_solo_female',
'code', 'name' # Supervisor compatibility
]
def to_representation(self, instance):
"""レスポンス形式を調整"""
data = super().to_representation(instance)
# publicフィールドからstatusへの移行サポート
if not data.get('status') and data.get('public'):
data['status'] = 'public'
elif not data.get('status'):
data['status'] = 'draft'
return data
class NewEventSerializer(serializers.ModelSerializer):
class Meta:
model = NewEvent
fields = ['event_name', 'start_datetime', 'end_datetime']
class TeamSerializer(serializers.ModelSerializer):
category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all())
#category = serializers.IntegerField()
#category = NewCategorySerializer(read_only=True)
#category_id = serializers.PrimaryKeyRelatedField(
# queryset=NewCategory.objects.all(),
# source='category',
# write_only=True
#)
owner = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Team
fields = ['id','team_name', 'category', 'owner']
read_only_fields = ['id', 'owner']
def to_representation(self, instance):
ret = super().to_representation(instance)
if instance.category:
ret['category'] = {
'id': instance.category.id,
'category_name': instance.category.category_name,
'category_number': instance.category.category_number,
'duration': instance.category.duration,
'num_of_member':instance.category.num_of_member,
'family':instance.category.family,
'female':instance.category.female
}
else:
ret['category'] = None
ret['owner'] = CustomUserSerializer(instance.owner).data
return ret
def validate_category(self, value):
if not isinstance(value, NewCategory):
raise serializers.ValidationError("Invalid category")
return value
#if not NewCategory.objects.filter(id=value).exists():
# raise serializers.ValidationError("Invalid category ID")
#return value
def create(self, validated_data):
return Team.objects.create(**validated_data)
#category_id = validated_data.pop('category')
#category = get_object_or_404(NewCategory, id=category_id)
#team = Team.objects.create(category=category, **validated_data)
#team.category = category
#return team
#category = validated_data.pop('category')
#team = Team.objects.create(category=category, **validated_data)
#return team
#logger.debug(f"Creating team with data: {validated_data}")
#validated_data['owner'] = self.context['request'].user
#return super().create(validated_data)
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
#if 'category' in validated_data:
# category_id = validated_data.pop('category')
# category = get_object_or_404(NewCategory, id=category_id)
# instance.category = category
#return super().update(instance, validated_data)
#if 'category' in validated_data:
# instance.category = validated_data.pop('category')
#return super().update(instance, validated_data)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female']
class EntrySerializer(serializers.ModelSerializer):
team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all())
event = serializers.PrimaryKeyRelatedField(queryset=NewEvent2.objects.all())
category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all())
owner = serializers.PrimaryKeyRelatedField(read_only=True)
date = serializers.DateTimeField(required=False, allow_null=True) # DateTimeFieldを使用
zekken_number = serializers.IntegerField()
class Meta:
model = Entry
fields = [
'id', 'team', 'event', 'category', 'date', 'zekken_number', 'owner',
'is_active', 'hasParticipated', 'hasGoaled',
# API変更要求書対応: 新フィールド追加
'staff_privileges', 'can_access_private_events', 'team_validation_status'
]
read_only_fields = ['id', 'owner']
def validate_date(self, value):
if isinstance(value, str):
try:
# 文字列をdatetimeオブジェクトに変換
value = datetime.strptime(value, "%Y-%m-%d")
except ValueError:
raise serializers.ValidationError("Invalid date format. Use YYYY-MM-DD.")
if isinstance(value, date) and not isinstance(value, datetime):
# dateオブジェクトをdatetimeオブジェクトに変換
value = datetime.combine(value, datetime.min.time())
if isinstance(value, datetime) and timezone.is_naive(value):
return timezone.make_aware(value, timezone.get_current_timezone())
return value
def validate_team(self, value):
if not value.members.exists():
raise serializers.ValidationError("チームにメンバーが登録されていません。")
return value
def validate(self, data):
team = data.get('team')
event = data.get('event')
category = data.get('category')
entry_date = data.get('date')
if isinstance(entry_date, datetime):
entry_date = entry_date.date()
elif isinstance(entry_date, str):
entry_date = datetime.strptime(entry_date, "%Y-%m-%d").date()
logger.debug("test-0")
logger.debug(f"==== start:{event.start_datetime.date()} <= entry_date : {entry_date} <= end:{event.end_datetime.date()} ?? ====")
if entry_date < event.start_datetime.date() or entry_date > event.end_datetime.date():
raise serializers.ValidationError(f"日付は{event.start_datetime.date()}から{event.end_datetime.date()}の間である必要があります。")
logger.debug("test-1")
try:
logger.debug(f"Parsed data: team={team}, event={event}, category={category}, ")
owner = self.context['request'].user
zekken_number = data.get('zekken_number')
logger.debug(f"entry_date={entry_date}, owner={owner}, zekken_number={zekken_number}")
except Exception:
raise serializers.ValidationError(f"何らかのエラーが発生しました")
# Check if team, event, and category exist
if not Team.objects.filter(id=team.id).exists():
raise serializers.ValidationError("指定されたチームは存在しません。")
if not NewEvent2.objects.filter(id=event.id).exists():
raise serializers.ValidationError("指定されたイベントは存在しません。")
if not NewCategory.objects.filter(id=category.id).exists():
raise serializers.ValidationError("指定されたカテゴリーは存在しません。")
# Check for unique constraint
if Entry.objects.filter(team=team, event=event, date__date=entry_date, owner=owner).exists():
raise serializers.ValidationError("既に登録済みです。")
# Validate zekken_number
if zekken_number is not None:
if zekken_number <= 0:
raise serializers.ValidationError("ゼッケン番号は正の整数である必要があります。")
# if Entry.objects.filter(event=event, zekken_number=zekken_number).exists():
# raise serializers.ValidationError("このゼッケン番号は既に使用されています。")
return data
def to_internal_value(self, data):
# dateフィールドが文字列で来た場合の処理
if 'date' in data and isinstance(data['date'], str):
try:
# 文字列をdatetimeオブジェクトに変換
data['date'] = datetime.strptime(data['date'], "%Y-%m-%d")
except ValueError:
raise serializers.ValidationError({"date": "無効な日付形式です。YYYY-MM-DD形式を使用してください。"})
return super().to_internal_value(data)
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['team'] = TeamSerializer(instance.team).data
ret['event'] = NewEvent2Serializer(instance.event).data
ret['category'] = NewCategorySerializer(instance.category).data
ret['owner'] = CustomUserSerializer(instance.owner).data
if isinstance(ret['date'], datetime):
ret['date'] = ret['date'].date().isoformat()
elif isinstance(ret['date'], date):
ret['date'] = ret['date'].isoformat()
return ret
#if isinstance(ret['date'], datetime):
# ret['date'] = ret['date'].date().isoformat()
#return ret
#def to_representation(self, instance):
# ret = super().to_representation(instance)
# ret['team'] = instance.team.team_name
# ret['event'] = instance.event.event_name
# ret['category'] = instance.category.category_name
# ret['owner'] = instance.owner.email
# return ret
class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_staff']
read_only_fields = ['id','email']
class TeamDetailSerializer(serializers.ModelSerializer):
category = NewCategorySerializer(read_only=True)
class Meta:
model = Team
fields = ['id', 'zekken_number', 'team_name', 'category']
class UserDetailSerializer(serializers.ModelSerializer):
event_date = serializers.SerializerMethodField()
last_goal_time = serializers.SerializerMethodField()
class Meta:
model = CustomUser
fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'is_staff', 'event_date', 'last_goal_time']
read_only_fields = ('id', 'email')
def get_event_date(self, obj):
"""イベント日付を取得(ハードコーディングの値を返す)"""
# ハードコーディングされた日付をDateTimeとして返す
from datetime import datetime
return datetime(2025, 5, 17).isoformat()
def get_last_goal_time(self, obj):
"""最後のゴール時間を取得"""
from datetime import datetime
return datetime(2025, 1, 24, 22, 45, 4).isoformat()
class UserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['firstname', 'lastname', 'date_of_birth', 'female']
extra_kwargs = {'email': {'read_only': True}}
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
class MemberCreationSerializer(serializers.Serializer):
#email = serializers.EmailField()
email = serializers.EmailField(allow_blank=True, required=False)
firstname = serializers.CharField(required=False, allow_blank=True)
lastname = serializers.CharField(required=False, allow_blank=True)
date_of_birth = serializers.DateField(required=False)
female = serializers.BooleanField(required=False)
class MemberWithUserSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Member
fields = ['user', 'team']
class MemberSerializer(serializers.ModelSerializer):
email = serializers.EmailField(write_only=True)
firstname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
lastname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
date_of_birth = serializers.DateField(required=False, allow_null=True)
female = serializers.BooleanField(required=False)
class Meta:
model = Member
fields = ['id', 'email', 'firstname', 'lastname', 'date_of_birth', 'female']
def validate_firstname(self, value):
return value or None
def validate_lastname(self, value):
return value or None
def create(self, validated_data):
email = validated_data.pop('email')
team = self.context['team']
# 既存のユーザーを探すか、新しいユーザーを作成
user, created = CustomUser.objects.get_or_create(email=email)
# ユーザーが新しく作成された場合のみ、追加情報を更新
if created:
user.firstname = validated_data.get('firstname', '')
user.lastname = validated_data.get('lastname', '')
user.date_of_birth = validated_data.get('date_of_birth')
user.female = validated_data.get('female', False)
user.save()
# メンバーを作成
member = Member.objects.create(
user=user,
team=team,
firstname=validated_data.get('firstname'),
lastname=validated_data.get('lastname'),
date_of_birth=validated_data.get('date_of_birth'),
female=validated_data.get('female', False)
)
return member
# メンバーを作成して返す
#class MemberCreationSerializerreturn Member.objects.create(user=user, team=team)
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user = instance.user
for attr, value in user_data.items():
setattr(user, attr, value)
user.save()
return super().update(instance, validated_data)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['email'] = instance.user.email
representation['firstname'] = instance.user.firstname
representation['lastname'] = instance.user.lastname
representation['date_of_birth'] = instance.user.date_of_birth
representation['female'] = instance.user.female
representation['is_staff'] = instance.user.is_staff
return representation
class MemberSerializerOld(serializers.ModelSerializer):
user = CustomUserSerializer(read_only=True)
firstname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
lastname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
date_of_birth = serializers.DateField(required=False, allow_null=True)
female = serializers.BooleanField(required=False)
#team = TeamDetailSerializer(read_only=True)
#email = serializers.EmailField(write_only=True, required=False)
#firstname = serializers.CharField(write_only=True, required=False)
#lastname = serializers.CharField(write_only=True, required=False)
#date_of_birth = serializers.DateField(write_only=True, required=False)
#female = serializers.BooleanField(write_only=True, required=False)
class Meta:
model = Member
fields = ['id','email','firstname','lastname','date_of_birth','female']
#read_only_fields = ['id', 'team']
'''
def create(self, validated_data):
team = validated_data['team']
email = validated_data.get('email')
if email.startswith('dummy_'):
user, _ = CustomUser.objects.get_or_create(
email=email,
defaults={**user_data, 'is_active': True}
)
else:
user, _ = CustomUser.objects.get_or_create(
email=email,
defaults={**user_data, 'is_active': False}
)
member = Member.objects.create(user=user, **validated_data)
return member
'''
def create(self, validated_data):
email = validated_data.pop('email')
team = self.context['team']
# 既存のユーザーを探すか、新しいユーザーを作成
user, created = CustomUser.objects.get_or_create(email=email)
# メンバーを作成
member = Member.objects.create(
user=user,
team=team,
firstname=validated_data.get('firstname', ''),
lastname=validated_data.get('lastname', ''),
date_of_birth=validated_data.get('date_of_birth'),
female=validated_data.get('female', False)
)
return member
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user = instance.user
for attr, value in user_data.items():
setattr(user, attr, value)
user.save()
return super().update(instance, validated_data)
#if user.email.startswith('dummy_'): # dummy_ で始まるメールアドレスの場合のみ更新
# for attr, value in user_data.items():
# setattr(user, attr, value)
# user.save()
#else:
# raise serializers.ValidationError("このユーザーの情報は更新できません。")
#return super().update(instance, validated_data)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['email'] = instance.user.email
return representation
'''
def to_representation(self, instance):
representation = super().to_representation(instance)
user_data = representation['user']
return {
'id': representation['id'],
'email': user_data['email'],
'firstname': user_data['firstname'],
'lastname': user_data['lastname'],
'date_of_birth': user_data['date_of_birth'],
'female': user_data['female'],
'team': representation['team']
}
'''
class EntryMemberSerializer(serializers.ModelSerializer):
class Meta:
model = EntryMember
fields = ['id', 'entry', 'member', 'is_temporary']
class TempUserSerializer(serializers.ModelSerializer):
class Meta:
model = TempUser
#fields = ['id','email', 'password', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code', 'created_at', 'expires_at']
fields = ['email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code']
class EntryCreationSerializer(serializers.Serializer):
owner_email = serializers.EmailField()
event_name = serializers.CharField()
category_name = serializers.CharField()
team_name = serializers.CharField()
zekken_number = serializers.CharField()
date = serializers.DateField()
members = serializers.ListField(child=serializers.DictField())
def create(self, validated_data):
owner = CustomUser.objects.get(email=validated_data['owner_email'])
event = NewEvent2.objects.get(event_name=validated_data['event_name'])
category = NewCategory.objects.get(category_name=validated_data['category_name'])
# Create or get team
team, _ = Team.objects.get_or_create(
zekken_number=validated_data['zekken_number'],
category=category,
defaults={'team_name': validated_data['team_name'], 'owner': owner}
)
# Create or update entry
entry, _ = Entry.objects.update_or_create(
owner=owner,
team=team,
event=event,
date=validated_data['date'],
defaults={'category': category}
)
# Process members
for member_data in validated_data['members']:
user, created = CustomUser.objects.get_or_create(
email=member_data.get('email'),
defaults={
'firstname': member_data['firstname'],
'lastname': member_data['lastname'],
'date_of_birth': member_data['date_of_birth']
}
)
if created:
TempUser.objects.create(
email=user.email,
firstname=user.firstname,
lastname=user.lastname,
date_of_birth=user.date_of_birth
)
# Send invitation email here
member, _ = NewMember.objects.get_or_create(
user=user,
team=team,
defaults={'is_temporary': created}
)
EntryMember.objects.get_or_create(entry=entry, member=member)
return entry
class PasswordResetRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
class PasswordResetConfirmSerializer(serializers.Serializer):
new_password = serializers.CharField(write_only=True)
confirm_password = serializers.CharField(write_only=True)
def validate(self, data):
if data['new_password'] != data['confirm_password']:
raise serializers.ValidationError("Passwords do not match")
validate_password(data['new_password'])
return data
class UserLastGoalTimeSerializer(serializers.Serializer):
user_email = serializers.EmailField()
last_goal_time = serializers.DateTimeField()
class LoginUserSerializer_old(serializers.Serializer):
identifier = serializers.CharField(required=True) # メールアドレスまたはゼッケン番号
password = serializers.CharField(required=True)
def validate(self, data):
identifier = data.get('identifier')
password = data.get('password')
if not identifier or not password:
raise serializers.ValidationError('認証情報を入力してください。')
# ゼッケン番号かメールアドレスかを判定
if '@' in identifier:
# メールアドレスの場合
user = authenticate(username=identifier, password=password)
else:
# ゼッケン番号の場合
try:
# ゼッケン番号からユーザーを検索
user = CustomUser.objects.filter(zekken_number=identifier).first()
if user:
# パスワード認証
if not user.check_password(password):
user = None
except ValueError:
user = None
if user and user.is_active:
return user
elif user and not user.is_active:
raise serializers.ValidationError('アカウントが有効化されていません。')
else:
raise serializers.ValidationError('認証情報が正しくありません。')
class AppVersionSerializer(serializers.ModelSerializer):
"""アプリバージョン管理シリアライザー"""
class Meta:
model = AppVersion
fields = [
'id', 'version', 'platform', 'build_number',
'is_latest', 'is_required', 'update_message',
'download_url', 'release_date', 'created_at'
]
read_only_fields = ['id', 'created_at']
class AppVersionCheckSerializer(serializers.Serializer):
"""アプリバージョンチェック用シリアライザー"""
current_version = serializers.CharField(max_length=20, help_text="現在のアプリバージョン")
platform = serializers.ChoiceField(
choices=[('android', 'Android'), ('ios', 'iOS')],
help_text="プラットフォーム"
)
build_number = serializers.CharField(max_length=20, required=False, help_text="ビルド番号")
class AppVersionResponseSerializer(serializers.Serializer):
"""アプリバージョンチェックレスポンス用シリアライザー"""
latest_version = serializers.CharField(help_text="最新バージョン")
update_required = serializers.BooleanField(help_text="強制更新が必要かどうか")
update_available = serializers.BooleanField(help_text="更新が利用可能かどうか")
update_message = serializers.CharField(help_text="更新メッセージ")
download_url = serializers.URLField(help_text="ダウンロードURL")
release_date = serializers.DateTimeField(help_text="リリース日時")
class UploadedImageSerializer(serializers.ModelSerializer):
"""画像アップロード情報シリアライザー"""
file_size_mb = serializers.ReadOnlyField()
class Meta:
model = UploadedImage
fields = [
'id', 'original_filename', 'server_filename', 'file_url',
'file_size', 'file_size_mb', 'mime_type', 'event_code',
'team_name', 'cp_number', 'upload_source', 'device_platform',
'capture_timestamp', 'upload_timestamp', 'device_info',
'processing_status', 'thumbnail_url', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'server_filename', 'file_url', 'upload_timestamp', 'created_at', 'updated_at']
class MultiImageUploadSerializer(serializers.Serializer):
"""マルチ画像アップロード用シリアライザー"""
event_code = serializers.CharField(max_length=50)
team_name = serializers.CharField(max_length=255)
cp_number = serializers.IntegerField()
images = serializers.ListField(
child=serializers.DictField(),
max_length=10, # 最大10ファイル
help_text="アップロードする画像情報のリスト"
)
upload_source = serializers.ChoiceField(
choices=['direct', 'sharing_intent', 'bulk_upload'],
default='direct'
)
device_platform = serializers.ChoiceField(
choices=['ios', 'android', 'web'],
required=False
)
def validate_images(self, value):
"""画像データの検証"""
if not value:
raise serializers.ValidationError("画像が指定されていません")
total_size = 0
for image_data in value:
# 必須フィールドチェック
required_fields = ['file_data', 'filename', 'mime_type', 'file_size']
for field in required_fields:
if field not in image_data:
raise serializers.ValidationError(f"画像データに{field}が含まれていません")
# ファイルサイズチェック
file_size = image_data.get('file_size', 0)
if file_size > 10485760: # 10MB
raise serializers.ValidationError(f"ファイル{image_data['filename']}のサイズが10MBを超えています")
total_size += file_size
# MIMEタイプチェック
allowed_types = ['image/jpeg', 'image/png', 'image/heic', 'image/webp']
if image_data.get('mime_type') not in allowed_types:
raise serializers.ValidationError(f"サポートされていないファイル形式: {image_data.get('mime_type')}")
# 合計サイズチェック50MB
if total_size > 52428800:
raise serializers.ValidationError("合計ファイルサイズが50MBを超えています")
return value
class MultiImageUploadResponseSerializer(serializers.Serializer):
"""マルチ画像アップロードレスポンス用シリアライザー"""
status = serializers.CharField()
uploaded_count = serializers.IntegerField()
failed_count = serializers.IntegerField()
uploaded_files = serializers.ListField(
child=serializers.DictField()
)
total_upload_size = serializers.IntegerField()
processing_time_ms = serializers.IntegerField()