From 9215ba8f9fbd489df2dd8baa38fa30304c40bdbe Mon Sep 17 00:00:00 2001 From: Akira Miyata Date: Fri, 26 Jul 2024 04:03:15 +0000 Subject: [PATCH] =?UTF-8?q?API=20debugging=201=20=E3=81=BE=E3=81=A0?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=81=82=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rog/migrations/0038_alter_entry_category.py | 19 ++ rog/models.py | 68 +++---- rog/serializers.py | 205 ++++++++++++++------ rog/views.py | 145 +++++++++++++- 4 files changed, 339 insertions(+), 98 deletions(-) create mode 100644 rog/migrations/0038_alter_entry_category.py diff --git a/rog/migrations/0038_alter_entry_category.py b/rog/migrations/0038_alter_entry_category.py new file mode 100644 index 0000000..f0ea1b3 --- /dev/null +++ b/rog/migrations/0038_alter_entry_category.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.9 on 2024-07-25 01:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0037_alter_member_team'), + ] + + operations = [ + migrations.AlterField( + model_name='entry', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newcategory'), + ), + ] diff --git a/rog/models.py b/rog/models.py index 1bf3efb..4149a93 100644 --- a/rog/models.py +++ b/rog/models.py @@ -369,40 +369,42 @@ class Entry(models.Model): }) # メンバーの年齢と性別をチェック - if self.team and not self.team.members.exists(): - raise ValidationError({'team': 'チームにメンバーが登録されていません。'}) + 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: - 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}名までの参加が必要です。") + #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: + 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): diff --git a/rog/serializers.py b/rog/serializers.py index 0cb88f8..4f5cb12 100644 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -18,6 +18,9 @@ from .utils import send_activation_email 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__) @@ -221,9 +224,10 @@ class NewEventSerializer(serializers.ModelSerializer): fields = ['event_name', 'start_datetime', 'end_datetime'] class TeamSerializer(serializers.ModelSerializer): - #category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all()) + category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all()) - category = NewCategorySerializer(read_only=True) + #category = serializers.IntegerField() + #category = NewCategorySerializer(read_only=True) #category_id = serializers.PrimaryKeyRelatedField( # queryset=NewCategory.objects.all(), # source='category', @@ -239,32 +243,60 @@ class TeamSerializer(serializers.ModelSerializer): def to_representation(self, instance): ret = super().to_representation(instance) - ret['category'] = { - 'id': instance.category.id, - 'category_name': instance.category.category_name, - 'category_number': instance.category.category_number - } + if instance.category: + ret['category'] = { + 'id': instance.category.id, + 'category_name': instance.category.category_name, + 'category_number': instance.category.category_number + } + else: + ret['category'] = None ret['owner'] = CustomUserSerializer(instance.owner).data return ret def validate_category(self, value): - if not NewCategory.objects.filter(id=value.id).exists(): - raise serializers.ValidationError("Invalid category ID.") + 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): - category = validated_data.pop('category') - team = Team.objects.create(category=category, **validated_data) - return team + 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): - if 'category' in validated_data: - instance.category = validated_data.pop('category') - return super().update(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: @@ -273,27 +305,98 @@ class CategorySerializer(serializers.ModelSerializer): class EntrySerializer(serializers.ModelSerializer): team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all()) - event = serializers.PrimaryKeyRelatedField(queryset=NewEvent.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.DateTimeField(default_timezone=timezone.get_current_timezone()) + class Meta: model = Entry fields = ['id','team', 'event', 'category', 'date','owner'] + 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') + owner = self.context['request'].user + + # 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("既に登録済みです。") + + 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'] = instance.team.team_name - ret['event'] = instance.event.event_name - ret['category'] = instance.category.category_name - ret['owner'] = instance.owner.email + 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 @@ -326,6 +429,15 @@ class UserUpdateSerializer(serializers.ModelSerializer): instance.save() return instance +class MemberCreationSerializer(serializers.Serializer): + #email = serializers.EmailField() + email = serializers.EmailField(allow_blank=True, required=False) + + firstname = serializers.CharField(required=False) + lastname = serializers.CharField(required=False) + date_of_birth = serializers.DateField(required=False) + female = serializers.BooleanField(required=False) + class MemberWithUserSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) @@ -335,7 +447,7 @@ class MemberWithUserSerializer(serializers.ModelSerializer): fields = ['user', 'team'] class MemberSerializer(serializers.ModelSerializer): - user = CustomUserSerializer() + user = CustomUserSerializer(read_only=True) team = TeamDetailSerializer(read_only=True) #email = serializers.EmailField(write_only=True, required=False) @@ -347,50 +459,29 @@ class MemberSerializer(serializers.ModelSerializer): class Meta: model = Member fields = ['id','user','team'] # ,'email','firstname','lastname','date_of_birth','female'] - #read_only_fields = ['id', 'team'] + read_only_fields = ['id', 'team'] + + ''' def create(self, validated_data): team = validated_data['team'] email = validated_data.get('email') - - if email: - 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.is_active = False - user.activation_token = str(uuid.uuid4()) - user.save() - activation_link = self.context['request'].build_absolute_uri( - reverse('user-activation', kwargs={'activation_token': user.activation_token}) - ) - try: - send_activation_email(user, activation_link) - except Exception as e: - logger.error(f"アクティベーションメールの送信中にエラーが発生しました: {str(e)}") - # メール送信に失敗しても、ユーザー作成は続行します - - else: - dummy_email = f"dummy_{uuid.uuid4()}@example.com" - user = CustomUser.objects.create( - email=dummy_email, - 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), - is_active=True + 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} ) - - try: - member = Member.objects.create(user=user, team=team) - except IntegrityError: - # ユーザーがすでにチームのメンバーの場合 - raise serializers.ValidationError("このユーザーは既にチームのメンバーです。") + member = Member.objects.create(user=user, **validated_data) return member + ''' + def update(self, instance, validated_data): user_data = validated_data.pop('user', {}) diff --git a/rog/views.py b/rog/views.py index 28fdbb1..f5f1f08 100644 --- a/rog/views.py +++ b/rog/views.py @@ -30,7 +30,7 @@ from django.http import JsonResponse from rest_framework.permissions import IsAuthenticated from django.contrib.gis.db.models import Extent, Union -from .serializers import TestSerialiser,NewEventSerializer,NewEvent2Serializer, TeamSerializer, NewCategorySerializer,CategorySerializer, EntrySerializer, MemberSerializer, TempUserSerializer, CustomUserSerializer,EntryMemberSerializer,EntryCreationSerializer +from .serializers import TestSerialiser,NewEventSerializer,NewEvent2Serializer, TeamSerializer, NewCategorySerializer,CategorySerializer, EntrySerializer, MemberSerializer, TempUserSerializer, CustomUserSerializer,EntryMemberSerializer,MemberCreationSerializer,EntryCreationSerializer from .models import TestModel from django.shortcuts import get_object_or_404 from django.db.models import F @@ -549,6 +549,25 @@ def CustomAreaNames(request): class UserActivationView(APIView): + + def get(self, request, activation_token): + try: + temp_user = TempUser.objects.get(verification_code=activation_token) + user = CustomUser.objects.create( + email=temp_user.email, + firstname=temp_user.firstname, + lastname=temp_user.lastname, + date_of_birth=temp_user.date_of_birth, + female=temp_user.female, + is_active=True + ) + # Here you might want to add the user to the team they were invited to + temp_user.delete() + return Response({"message": "アカウントが正常にアクティベートされました。"}, status=status.HTTP_200_OK) + except TempUser.DoesNotExist: + return Response({"error": "無効なアクティベーショントークンです。"}, status=status.HTTP_400_BAD_REQUEST) + + ''' def get(self, request, activation_token): try: user = CustomUser.objects.get(activation_token=activation_token, is_active=False) @@ -558,6 +577,7 @@ class UserActivationView(APIView): return Response({"message": "アカウントが正常にアクティベートされました。"}, status=status.HTTP_200_OK) except CustomUser.DoesNotExist: return Response({"error": "無効なアクティベーショントークンです。"}, status=status.HTTP_400_BAD_REQUEST) + ''' class ChangePasswordView(generics.UpdateAPIView): """ @@ -721,7 +741,7 @@ class CategoryListView(generics.ListAPIView): ''' class EntryViewSet(viewsets.ModelViewSet): - queryset = Entry.objects.all() + #queryset = Entry.objects.all() serializer_class = EntrySerializer permission_classes = [permissions.IsAuthenticated] @@ -744,10 +764,14 @@ class EntryViewSet(viewsets.ModelViewSet): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) except DRFValidationError as e: - return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + return Response({'error': self.format_errors(e.detail)}, status=status.HTTP_400_BAD_REQUEST) + # except IntegrityError: + # return Response({'error': '既に登録済みです'}, status=status.HTTP_400_BAD_REQUEST) + except IntegrityError as e: + return Response({'error': f'データベースエラー: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) - #except ValidationError as e: - # return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return Response({'error': f"予期せぬエラーが発生しました: {str(e),'type': str(type(e))}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def perform_create(self, serializer): serializer.save(owner=self.request.user) @@ -765,14 +789,88 @@ class EntryViewSet(viewsets.ModelViewSet): self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) + def get_error_message(self, exception): + if hasattr(exception, 'detail'): + if isinstance(exception.detail, dict): + return '. '.join([f"{key}: {', '.join(value)}" for key, value in exception.detail.items()]) + elif isinstance(exception.detail, list): + return '. '.join(exception.detail) + return str(exception) + + def format_errors(self, errors): + if isinstance(errors, list): + return '. '.join(errors) + elif isinstance(errors, dict): + return '. '.join([f"{key}: {value}" if isinstance(value, str) else f"{key}: {', '.join(value)}" for key, value in errors.items()]) + else: + return str(errors) + + class MemberViewSet(viewsets.ModelViewSet): - serializer_class = MemberSerializer + #serializer_class = MemberSerializer permission_classes = [permissions.IsAuthenticated,IsTeamOwner] + def get_serializer_class(self): + if self.action == 'create': + return MemberCreationSerializer + return MemberSerializer + def get_queryset(self): team_id = self.kwargs['team_id'] return Member.objects.filter(team_id=team_id) + def create(self, request, *args, **kwargs): + team = Team.objects.get(id=self.kwargs['team_id']) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + email = serializer.validated_data.get('email', '') + + user_data = { + 'firstname': serializer.validated_data.get('firstname', ''), + 'lastname': serializer.validated_data.get('lastname', ''), + 'date_of_birth': serializer.validated_data.get('date_of_birth'), + 'female': serializer.validated_data.get('female', False), + 'is_active': True + } + + if not email or email.startswith('dummy_'): + # 直接登録 + if email: + user, created = CustomUser.objects.get_or_create( + email=email, + defaults=user_data + ) + else: + # emailが空の場合、一意のダミーメールアドレスを生成 + dummy_email = f"dummy_{uuid.uuid4()}@example.com" + user = CustomUser.objects.create(email=dummy_email, **user_data) + + member = Member.objects.create(user=user, team=team) + return Response(MemberSerializer(member).data, status=status.HTTP_201_CREATED) + + #member = serializer.save(team=team) + #return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + # 仮登録 + existing_user = CustomUser.objects.filter(email=email).first() + if existing_user: + # 既存ユーザーの場合、チーム招待メールを送信 + self.send_invitation_email(existing_user, team) + return Response({"message": "Invitation sent to existing user."}, status=status.HTTP_200_OK) + else: + print("新規ユーザー") + # 新規ユーザーの場合、TempUserを作成してアクティベーションメールを送信 + temp_user = TempUser.objects.create( + email=email, + **user_data, + verification_code=str(uuid.uuid4()) + ) + self.send_activation_email(temp_user, team) + return Response({"message": "Verification email sent to the user."}, status=status.HTTP_201_CREATED) + + + ''' def create(self, request, *args, **kwargs): team = Team.objects.get(id=self.kwargs['team_id']) serializer = self.get_serializer(data=request.data) @@ -788,6 +886,37 @@ class MemberViewSet(viewsets.ModelViewSet): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + ''' + + def send_activation_email(self, temp_user, team): + # アクティベーションメール送信のロジックをここに実装 + # 注意: 'user-activation' URLパターンが存在しない場合は、適切なURLを生成する方法を検討する必要があります + + activation_url = "dummy url" #f"{settings.FRONTEND_URL}/activate/{temp_user.verification_code}" + + # ここでemailを送信するロジックを実装 + print(f"Activation email would be sent to {temp_user.email} with URL: {activation_url}") + + + def send_invitation_email(self, user, team): + # チーム招待メール送信のロジック + #invitation_url = self.request.build_absolute_uri( + # reverse('team-invite', kwargs={'team_id': team.id, 'user_id': user.id}) + #) + #subject = f"Invitation to join {team.name}" + #message = f"You have been invited to join the team {team.name}. Click here to accept: {invitation_url}" + #from_email = settings.DEFAULT_FROM_EMAIL + #recipient_list = [user.email] + + #send_mail(subject, message, from_email, recipient_list) + + # チーム招待メール送信のロジックをここに実装 + # 注意: 'team-invitation' URLパターンが存在しない場合は、適切なURLを生成する方法を検討する必要があります + # 例えば、フロントエンドのURLを直接指定する方法など + invitation_url = "dummy url" #f"{settings.FRONTEND_URL}/team/{team.id}/invite" + # ここでemailを送信するロジックを実装 + print(f"Invitation email would be sent to {user.email} with URL: {invitation_url}") + def perform_create(self, serializer): @@ -905,12 +1034,12 @@ class UserEntriesView(generics.ListAPIView): # イベントのカテゴリーを取得するビュー class EventCategoriesView(generics.ListAPIView): - serializer_class = CategorySerializer + serializer_class = NewCategorySerializer permission_classes = [IsAuthenticated] def get_queryset(self): event_id = self.kwargs['event_id'] - return Category.objects.filter(entry__event_id=event_id).distinct() + return NewCategory.objects.filter(entry__event_id=event_id).distinct() class RegisterView(APIView): def post(self, request):