diff --git a/config/settings.py b/config/settings.py index 29cbe94..f8034db 100644 --- a/config/settings.py +++ b/config/settings.py @@ -176,14 +176,21 @@ REST_FRAMEWORK = { } +FRONTEND_URL = 'http://rogainig.intranet.sumasen.net/' # フロントエンドのURLに適宜変更してください + +# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = 'smtp.outlook.com' EMAIL_PORT = 587 EMAIL_USE_TLS = True -EMAIL_HOST_USER = 'akira.miyata@gifuai.net' -EMAIL_HOST_PASSWORD = 'SachikoMiyata123' +EMAIL_HOST_USER = 'rogaining@gifuai.net' +EMAIL_HOST_PASSWORD = 'ctcpy9823"x~' DEFAULT_FROM_EMAIL = 'info@gifuai.net' +APP_DOWNLOAD_LINK = 'http://your-app-download-link.com' +SERVICE_NAME = '岐阜ロゲ' + # settings.py DEFAULT_CHARSET = 'utf-8' @@ -194,4 +201,43 @@ DEFAULT_CHARSET = 'utf-8' # 'JSON_UNICODE_ESCAPE': False, #} - +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {message}', + 'style': '{', + }, + }, + 'handlers': { + #'file': { + # 'level': 'DEBUG', + # 'class': 'logging.FileHandler', + # 'filename': os.path.join(BASE_DIR, 'logs/debug.log'), + # 'formatter': 'verbose', + #}, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'DEBUG', + }, + 'loggers': { + #'django': { + # 'handlers': ['console'], + # 'level': 'INFO', + # 'propagate': False, + #}, + 'rog': { + #'handlers': ['file','console'], + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, +} diff --git a/rog/migrations/0039_auto_20240726_1508.py b/rog/migrations/0039_auto_20240726_1508.py new file mode 100644 index 0000000..828a97b --- /dev/null +++ b/rog/migrations/0039_auto_20240726_1508.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.9 on 2024-07-26 06:08 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0038_alter_entry_category'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='date_joined', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='customuser', + name='email', + field=models.EmailField(max_length=254, unique=True), + ), + migrations.AlterField( + model_name='customuser', + name='group', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/rog/models.py b/rog/models.py index 4149a93..52ce5ab 100644 --- a/rog/models.py +++ b/rog/models.py @@ -208,26 +208,24 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): GB2 = '大垣-3時間', '大垣-3時間' GB3 = '大垣-5時間', '大垣-5時間' - email = models.CharField(_("Email"), max_length=255, unique=True) - is_staff = models.BooleanField(default=False) - is_active = models.BooleanField(default=True) - is_rogaining = models.BooleanField(default=False) - zekken_number = models.CharField(_("Zekken Number"), max_length=255, blank=True, null=True) - event_code = models.CharField(_("Event Code"), max_length=255, blank=True, null=True) - team_name = models.CharField(_("Team Name"), max_length=255, blank=True, null=True) - group = models.CharField(max_length=255, - choices=Groups.choices, - default=Groups.GB1) - - # Akira + email = models.EmailField(unique=True) firstname = models.CharField(max_length=255,blank=True, null=True) lastname = models.CharField(max_length=255, blank=True, null=True) date_of_birth = models.DateField(blank=True, null=True) female = models.BooleanField(default=False) - # Akira + group = models.CharField(max_length=255,blank=True) + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + date_joined = models.DateTimeField(default=timezone.now) + + is_rogaining = models.BooleanField(default=False) + zekken_number = models.CharField(_("Zekken Number"), max_length=255, blank=True, null=True) + event_code = models.CharField(_("Event Code"), max_length=255, blank=True, null=True) + team_name = models.CharField(_("Team Name"), max_length=255, blank=True, null=True) + USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['group',] + REQUIRED_FIELDS = [] objects = CustomUserManager() diff --git a/rog/serializers.py b/rog/serializers.py index 4f5cb12..92b3ade 100644 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -1,8 +1,8 @@ import uuid from django.db import IntegrityError -from django.core.mail import send_mail from django.conf import settings from django.urls import reverse +from django.contrib.auth.password_validation import validate_password from django.db import transaction from rest_framework import serializers @@ -14,7 +14,6 @@ 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 .utils import send_activation_email from .models import TestModel import logging @@ -72,8 +71,41 @@ class GifuAreaSerializer(serializers.ModelSerializer): 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]) + + class Meta: + model = CustomUser + fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female') + extra_kwargs = { + 'email': {'required': True}, + 'firstname': {'required': True}, + 'lastname': {'required': True}, + 'date_of_birth': {'required': True}, + } + + 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): + try: + user = CustomUser.objects.create_user( + email=validated_data['email'], + 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='' # この値は必要に応じて変更してください + ) + return user + except ValidationError as e: + raise serializers.ValidationError({"password": list(e.messages)}) +''' class CreateUserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser @@ -83,6 +115,17 @@ class CreateUserSerializer(serializers.ModelSerializer): def create(self, validated_data): user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','') return user +''' + +class TempUserRegistrationSerializer(serializers.ModelSerializer): + class Meta: + model = TempUser + fields = ('email', 'firstname', 'lastname', 'date_of_birth', 'female') + + def create(self, validated_data): + validated_data['verification_code'] = str(uuid.uuid4()) + return TempUser.objects.create(**validated_data) + class UserSerializer(serializers.ModelSerializer): class Meta: @@ -385,10 +428,16 @@ class EntrySerializer(serializers.ModelSerializer): def to_representation(self, instance): ret = super().to_representation(instance) - if isinstance(ret['date'], datetime): - ret['date'] = ret['date'].date().isoformat() + ret['team'] = TeamSerializer(instance.team).data + ret['event'] = NewEvent2Serializer(instance.event).data + ret['category'] = NewCategorySerializer(instance.category).data + ret['owner'] = CustomUserSerializer(instance.owner).data 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 @@ -400,9 +449,8 @@ class EntrySerializer(serializers.ModelSerializer): class CustomUserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - #fields = ['email', 'is_staff', 'is_active', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female'] fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female'] - read_only_fields = ['email'] + read_only_fields = ['id','email'] class TeamDetailSerializer(serializers.ModelSerializer): category = NewCategorySerializer(read_only=True) @@ -448,7 +496,7 @@ class MemberWithUserSerializer(serializers.ModelSerializer): class MemberSerializer(serializers.ModelSerializer): user = CustomUserSerializer(read_only=True) - team = TeamDetailSerializer(read_only=True) + #team = TeamDetailSerializer(read_only=True) #email = serializers.EmailField(write_only=True, required=False) #firstname = serializers.CharField(write_only=True, required=False) @@ -487,16 +535,35 @@ class MemberSerializer(serializers.ModelSerializer): user_data = validated_data.pop('user', {}) user = instance.user - if user.email.startswith('dummy_'): # dummy_ で始まるメールアドレスの場合のみ更新 - for attr, value in user_data.items(): - setattr(user, attr, value) - user.save() - else: - raise serializers.ValidationError("このユーザーの情報は更新できません。") + 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) + 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: @@ -507,7 +574,8 @@ class EntryMemberSerializer(serializers.ModelSerializer): 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 = ['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() diff --git a/rog/templates/email/activation_friend_email.txt b/rog/templates/email/invitation_existing_email.txt similarity index 100% rename from rog/templates/email/activation_friend_email.txt rename to rog/templates/email/invitation_existing_email.txt diff --git a/rog/templates/email/invitation_new_email.txt b/rog/templates/email/invitation_new_email.txt new file mode 100644 index 0000000..24f0cc7 --- /dev/null +++ b/rog/templates/email/invitation_new_email.txt @@ -0,0 +1,16 @@ +件名: アカウントのアクティベーション + +{name} 様 + +この度は、サービスにご登録いただき、ありがとうございます。 + +以下のリンクをクリックしてアカウントをアクティベートしてください: +{activation_link} + +また、アプリをダウンロードしてください: +{app_download_link} + +このメールに心当たりがない場合は、お手数ですが破棄してください。 +よろしくお願いいたします。 + +{service_name} チーム diff --git a/rog/templates/email/activation_email.txt b/rog/templates/email/verification_email.txt similarity index 100% rename from rog/templates/email/activation_email.txt rename to rog/templates/email/verification_email.txt diff --git a/rog/urls.py b/rog/urls.py index 69fb1a8..8b0f544 100644 --- a/rog/urls.py +++ b/rog/urls.py @@ -1,7 +1,7 @@ from sys import prefix from rest_framework import urlpatterns from rest_framework.routers import DefaultRouter -from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView +from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView from django.urls import path, include from knox import views as knox_views @@ -57,7 +57,7 @@ urlpatterns += [ path('mainperfext/', ExtentForMainPerf, name="main_perf_ext"), path('cats/', CatView, name='cats'), path('catbycity/', CatByCity, name='cat_by_city'), - path('register/', RegistrationAPI.as_view()), + #path('register/', RegistrationAPI.as_view()), path('login/', LoginAPI.as_view()), path('user/', UserAPI.as_view()), path('logout/', knox_views.LogoutView.as_view(), name='knox_logout'), @@ -71,9 +71,7 @@ urlpatterns += [ path('change-password/', ChangePasswordView.as_view(), name='change-password'), path('delete-account/', DeleteAccount, name="delete-account"), path('privacy/', PrivacyView, name='privacy-view'), - path('register', RegistrationView.as_view(), name='register'), # path('goal-image/', GoalImageViewSet.as_view(), name='goal-image') - path('register/', RegisterView.as_view(), name='register'), path('verify-email//', VerifyEmailView.as_view(), name='verify-email'), path('categories/', NewCategoryListView.as_view(), name='category-list'), path('new-events/', NewEvent2ListView.as_view(), name='new-event-list'), @@ -83,5 +81,8 @@ urlpatterns += [ path('teams//members/', MemberViewSet.as_view({'get': 'list', 'post': 'create'}), name='team-members'), path('teams//members//', MemberViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}), name='team-member-detail'), path('activate//', UserActivationView.as_view(), name='user-activation'), + #path('register/', RegistrationView.as_view(), name='register'), #直接の登録はしない。 + path('register/', TempUserRegistrationView.as_view(), name='temp-register'), # 仮登録 + #path('register/temp/', RegisterView.as_view(), name='register'), # 古い仮登録 ] diff --git a/rog/utils.py b/rog/utils.py index 2c46097..5e5fb7e 100644 --- a/rog/utils.py +++ b/rog/utils.py @@ -16,7 +16,9 @@ def load_email_template(template_name, context): return subject, body -def send_activation_email(user, activation_link): +# 既にユーザーになっている人にチームへの参加要請メールを出す。 +# +def send_team_join_email(sender,user,team,entry,activation_link): context = { 'name': user.firstname or user.email, 'activation_link': activation_link, @@ -26,9 +28,73 @@ def send_activation_email(user, activation_link): subject, body = load_email_template('activation_email.txt', context) + try: + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False) + logger.info(f"チームへの参加承認メールを送信しました。 受信者: {user.email}") + except Exception as e: + logger.error(f"チームへの参加承認メールの送信に失敗しました。 受信者: {user.email}, エラー: {str(e)}") + raise # エラーを再度発生させて、呼び出し元で処理できるようにします + +# 自らユーザー登録した際に、メールの確認メールを送る。 +# +def send_verification_email(user, activation_link): + context = { + 'name': user.firstname or user.email, + 'activation_link': activation_link, + 'app_download_link': settings.APP_DOWNLOAD_LINK, + 'service_name': settings.SERVICE_NAME, + } + + subject, body = load_email_template('verification_email.txt', context) + + logger.info(f"subject: {subject}") + logger.info(f"body: {body}") + try: send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False) logger.info(f"アクティベーションメールを送信しました。 受信者: {user.email}") except Exception as e: logger.error(f"アクティベーションメールの送信に失敗しました。 受信者: {user.email}, エラー: {str(e)}") raise # エラーを再度発生させて、呼び出し元で処理できるようにします + + +# まだユーザーでない人にチームメンバー招待メールを送る +# その人がユーザー登録して、ユーザー登録されるとメンバーになる。 +# アプリからユーザー登録するため、アプリのダウンロードリンクも送る。 +# +def send_invitation_email(sender,user,team,entry,activation_link): + context = { + 'name': user.firstname or user.email, + 'invitaion_link': activation_link, + 'app_download_link': settings.APP_DOWNLOAD_LINK, + 'service_name': settings.SERVICE_NAME, + } + + subject, body = load_email_template('invitaion_email.txt', context) + + try: + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False) + logger.info(f"招待メールを送信しました。 受信者: {user.email}") + except Exception as e: + logger.error(f"招待メールの送信に失敗しました。 受信者: {user.email}, エラー: {str(e)}") + raise # エラーを再度発生させて、呼び出し元で処理できるようにします + +# エントリーしたら、その内容をメンバーに送信する。 +# +def send_entry_email(sender,user,team,entry,activation_link): + context = { + 'name': user.firstname or user.email, + 'invitaion_link': activation_link, + 'app_download_link': settings.APP_DOWNLOAD_LINK, + 'service_name': settings.SERVICE_NAME, + } + + subject, body = load_email_template('invitaion_email.txt', context) + + try: + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False) + logger.info(f"エントリーメールを送信しました。 受信者: {user.email}") + except Exception as e: + logger.error(f"エントリーメールの送信に失敗しました。 受信者: {user.email}, エラー: {str(e)}") + raise # エラーを再度発生させて、呼び出し元で処理できるようにします + diff --git a/rog/views.py b/rog/views.py index f5f1f08..39df5fa 100644 --- a/rog/views.py +++ b/rog/views.py @@ -1,7 +1,8 @@ from rest_framework import serializers from django.db import IntegrityError from django.urls import reverse -from .utils import send_activation_email +from .utils import send_verification_email,send_invitation_email,send_team_join_email,send_entry_email +from django.conf import settings import uuid from rest_framework.exceptions import ValidationError as DRFValidationError @@ -12,14 +13,14 @@ from rest_framework.decorators import action from rest_framework.response import Response from django.shortcuts import get_object_or_404 from .models import Team, Member, CustomUser, NewCategory -from .serializers import TeamSerializer, MemberSerializer, CustomUserSerializer, TeamDetailSerializer,UserUpdateSerializer +from .serializers import TeamSerializer, MemberSerializer, CustomUserSerializer, TeamDetailSerializer,UserUpdateSerializer,UserRegistrationSerializer from .permissions import IsTeamOwner from curses.ascii import NUL from django.core.serializers import serialize from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser,EntryMember from rest_framework import viewsets -from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, CreateUserSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer +from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer from knox.models import AuthToken from rest_framework import viewsets, generics, status @@ -50,6 +51,9 @@ from django.utils.decorators import method_decorator from django.utils.encoding import force_str import logging +from datetime import datetime + +from django.utils.dateparse import parse_date logger = logging.getLogger(__name__) @@ -284,7 +288,8 @@ def CatByCity(request): class RegistrationAPI(generics.GenericAPIView): - serializer_class = CreateUserSerializer + #serializer_class = CreateUserSerializer + serializer_class = UserRegistrationSerializer def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -410,6 +415,20 @@ def DeleteAccount(request): return Response({"result":"user deleted"}) return Response({"result":"user not found"}) +''' + def send_verification_email(self, temp_user): + subject = '仮登録の確認' + message = f'以下のリンクをクリックして登録を完了してください:\n{settings.FRONTEND_URL}/verify/{temp_user.verification_code}' + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [temp_user.email] + + try: + send_mail(subject, message, from_email, recipient_list) + logger.info(f"Verification email sent to {temp_user.email}") + except Exception as e: + logger.error(f"Failed to send verification email to {temp_user.email}. Error: {str(e)}") +''' + def UserActionViewset(request): user_id = request.GET.get('user_id') @@ -622,13 +641,25 @@ class TestActionViewSet(viewsets.ModelViewSet): def PrivacyView(request): return render(request, "rog/privacy.html") + class RegistrationView(APIView): + @transaction.atomic def post(self, request): - serializer = RegistrationSerializer(data=request.data) + serializer = UserRegistrationSerializer(data=request.data) if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + try: + user = serializer.save() + logger.info(f"New user registered: {user.email}") + return Response({"message": "ユーザー登録が完了しました。"}, status=status.HTTP_201_CREATED) + except Exception as e: + logger.error(f"Error during user registration: {str(e)}") + return Response({"error": "ユーザー登録中にエラーが発生しました。"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + logger.warning(f"Invalid registration data: {serializer.errors}") + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + # Akira @@ -751,10 +782,13 @@ class EntryViewSet(viewsets.ModelViewSet): def get_queryset(self): user = self.request.user - # ユーザーが所属するチームのIDを取得 - team_ids = Member.objects.filter(user=user).values_list('team_id', flat=True) - # そのチームに関連するエントリーを取得 - return Entry.objects.filter(team__id__in=team_ids) + # ユーザーが所有するチームのIDを取得 + owned_team_ids = Team.objects.filter(owner=user).values_list('id', flat=True) + # ユーザーがメンバーとして所属するチームのIDを取得 + member_team_ids = Member.objects.filter(user=user).values_list('team_id', flat=True) + # 両方のチームに関連するエントリーを取得 + return Entry.objects.filter(Q(team__id__in=owned_team_ids) | Q(team__id__in=member_team_ids)) + def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -831,7 +865,6 @@ class MemberViewSet(viewsets.ModelViewSet): '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_'): @@ -839,7 +872,7 @@ class MemberViewSet(viewsets.ModelViewSet): if email: user, created = CustomUser.objects.get_or_create( email=email, - defaults=user_data + defaults={**user_data, 'is_active':True} ) else: # emailが空の場合、一意のダミーメールアドレスを生成 @@ -856,18 +889,17 @@ class MemberViewSet(viewsets.ModelViewSet): 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) + send_team_join_email(existing_user, team) + return Response({"message": "Invitation for your team 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) + send_invitation_email(temp_user, team) + return Response({"message": "Invitation email sent to the user."}, status=status.HTTP_201_CREATED) ''' @@ -888,6 +920,7 @@ class MemberViewSet(viewsets.ModelViewSet): return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) ''' + ''' def send_activation_email(self, temp_user, team): # アクティベーションメール送信のロジックをここに実装 # 注意: 'user-activation' URLパターンが存在しない場合は、適切なURLを生成する方法を検討する必要があります @@ -897,7 +930,6 @@ class MemberViewSet(viewsets.ModelViewSet): # ここで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( @@ -916,6 +948,7 @@ class MemberViewSet(viewsets.ModelViewSet): 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}") + ''' @@ -923,25 +956,60 @@ class MemberViewSet(viewsets.ModelViewSet): team = Team.objects.get(id=self.kwargs['team_id']) serializer.save(team=team) + @transaction.atomic def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) + #partial = kwargs.pop('partial', False) instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - serializer.is_valid(raise_exception=True) + user = instance.user + logger.debug(f"Updating user: {user.email}") + logger.debug(f"Received data: {request.data}") - if not instance.user.email.startswith('dummy_'): - return Response({"error": "このユーザーの情報は更新できません。"}, status=status.HTTP_400_BAD_REQUEST) + if user.email.startswith('dummy_'): + logger.debug("User has dummy email, proceeding with update") + # 直接データを更新 + user.firstname = request.data.get('firstname', user.firstname) + user.lastname = request.data.get('lastname', user.lastname) + + # 日付の処理 + date_of_birth = request.data.get('date_of_birth') + if date_of_birth: + try: + date_of_birth = date_of_birth.translate(str.maketrans("0123456789", "0123456789")) + parsed_date = parse_date(date_of_birth) + if parsed_date: + user.date_of_birth = parsed_date + else: + raise ValueError("Invalid date format") + except ValueError: + logger.error(f"Invalid date format: {date_of_birth}") + return Response({"error": "Invalid date format. Use YYYY-MM-DD."}, status=status.HTTP_400_BAD_REQUEST) - self.perform_update(serializer) + user.female = request.data.get('female', user.female) + + user.save() + logger.debug(f"User updated: firstname={user.firstname}, lastname={user.lastname}, date_of_birth={user.date_of_birth}, female={user.female}") + + # Memberインスタンスも更新 + serializer = self.get_serializer(instance, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) + + # 更新後のデータを取得 + updated_instance = self.get_object() + updated_serializer = self.get_serializer(updated_instance) + + logger.debug(f"Updated user data: {updated_serializer.data}") + return Response(updated_serializer.data) + else: + logger.debug("User does not have dummy email, update not allowed") + return Response({"error": "このユーザーの情報は更新できません。"}, status=status.HTTP_403_FORBIDDEN) - if getattr(instance, '_prefetched_objects_cache', None): - instance._prefetched_objects_cache = {} - - return Response(serializer.data) def perform_update(self, serializer): serializer.save() + logger.debug("perform_update called") + def get_object(self): queryset = self.get_queryset() @@ -1041,6 +1109,7 @@ class EventCategoriesView(generics.ListAPIView): event_id = self.kwargs['event_id'] return NewCategory.objects.filter(entry__event_id=event_id).distinct() +# ユーザー仮登録 class RegisterView(APIView): def post(self, request): serializer = TempUserSerializer(data=request.data) @@ -1053,16 +1122,33 @@ class RegisterView(APIView): verification_url = request.build_absolute_uri( reverse('verify-email', kwargs={'verification_code': verification_code}) ) - send_mail( - 'Verify your email', - f'Click the link to verify your email: {verification_url}', - settings.DEFAULT_FROM_EMAIL, - [temp_user.email], - fail_silently=False, - ) + send_verification_email(temp_user,verifiction_url) + #send_mail( + # 'Verify your email', + # f'Click the link to verify your email: {verification_url}', + # settings.DEFAULT_FROM_EMAIL, + # [temp_user.email], + # fail_silently=False, + #) return Response({'message': 'Verification email sent'}, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +class TempUserRegistrationView(APIView): + def post(self, request): + serializer = TempUserRegistrationSerializer(data=request.data) + if serializer.is_valid(): + temp_user = serializer.save() + verification_code = uuid.uuid4() + temp_user.verification_code = verification_code + temp_user.save() + verification_url = request.build_absolute_uri( + reverse('verify-email', kwargs={'verification_code': verification_code}) + ) + send_verification_email(temp_user,verification_url) #招待メールを送る。 + return Response({"message": "仮登録が完了しました。招待メールを送信しました。"}, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +# アクティベーション class VerifyEmailView(APIView): def get(self, request, verification_code): try: