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() class Meta: model=Location2025 fields=[ # 基本フィールド 'id', 'cp_number', 'event', 'cp_name', 'sub_loc_id', 'subcategory', 'latitude', 'longitude', 'location', 'address', 'checkin_point', 'buy_point', 'checkin_radius', 'auto_checkin', 'shop_closed', 'shop_shutdown', 'opening_hours', 'phone', 'website', 'description', 'remark', # 追加フィールド 'photos', 'videos', 'tags', 'evaluation_value', 'hidden_location', # 管理フィールド 'is_active', 'sort_order', 'csv_source_file', 'csv_upload_date', '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' ] 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') 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(input_formats=['%Y-%m-%d']) date = serializers.DateField(required=False, allow_null=True) # DateTimeFieldではなくDateFieldを使用 zekken_number = serializers.IntegerField() #date = serializers.DateTimeField(default_timezone=timezone.get_current_timezone()) 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: value = datetime.strptime(value, "%Y-%m-%d") except ValueError: raise serializers.ValidationError("Invalid date format. Use YYYY-MM-DD.") if isinstance(value, date): value = datetime.combine(value, datetime.min.time()) if timezone.is_naive(value): return timezone.make_aware(value, timezone.get_current_timezone()) return value #if isinstance(value, date): # # dateオブジェクトをdatetimeオブジェクトに変換 # value = datetime.combine(value, datetime.min.time()) #if 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_date(self, value): if isinstance(value, datetime): return value.date() 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'] 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 UserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group'] read_only_fields = ('id', 'email') 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 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()