Release 4-8-6
This commit is contained in:
@ -71,7 +71,7 @@ ROOT_URLCONF = 'config.urls'
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
@ -176,8 +176,8 @@ REST_FRAMEWORK = {
|
||||
}
|
||||
|
||||
|
||||
#FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||
FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||
FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||
#FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||
|
||||
# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
|
||||
@ -155,17 +155,22 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
# return user
|
||||
|
||||
class TempUserRegistrationSerializer(serializers.ModelSerializer):
|
||||
password = serializers.CharField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TempUser
|
||||
fields = ('email', 'firstname', 'lastname', 'date_of_birth', 'female')
|
||||
fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female')
|
||||
|
||||
def create(self, 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
|
||||
# パスワードのハッシュ化は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:
|
||||
@ -793,4 +798,16 @@ class EntryCreationSerializer(serializers.Serializer):
|
||||
|
||||
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
|
||||
|
||||
|
||||
23
rog/templates/email/reset_password_email.txt
Normal file
23
rog/templates/email/reset_password_email.txt
Normal file
@ -0,0 +1,23 @@
|
||||
件名: 岐阜ロゲのパスワードリセットのお知らせ
|
||||
|
||||
{{name}} 様
|
||||
|
||||
こちらは岐阜aiネットワークのAI担当です。
|
||||
|
||||
このメールはパスワードのリセットのご依頼によるリセット確認メールです。
|
||||
身に覚えのない方は、お手数ですが削除をお願いします。
|
||||
|
||||
以下のリンクからパスワードのリセットが行えます。
|
||||
|
||||
{{activation_link}}
|
||||
|
||||
|
||||
それでは、今後とも岐阜ロゲをよろしくお願いいたします。
|
||||
|
||||
|
||||
※ 本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承ください(ご質問等はinfo@gifuai.netまでお願いいたします)。もしこのメールに心当たりがない場合は破棄願います。
|
||||
|
||||
NPO岐阜aiネットワーク 担当AI
|
||||
|
||||
|
||||
|
||||
116
rog/templates/password-reset-component.tsx
Normal file
116
rog/templates/password-reset-component.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
|
||||
const PasswordReset = () => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const { uid, token } = useParams();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (password !== confirmPassword) {
|
||||
setMessage('パスワードが一致しません。');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/reset-password/${uid}/${token}/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ new_password: password }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
setMessage('パスワードが正常にリセットされました。');
|
||||
} else {
|
||||
setMessage(data.message || 'パスワードのリセットに失敗しました。');
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage('エラーが発生しました。もう一度お試しください。');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
パスワードのリセット
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
||||
新しいパスワード
|
||||
</label>
|
||||
<div className="mt-1 relative">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-5 w-5 text-gray-400" />
|
||||
) : (
|
||||
<Eye className="h-5 w-5 text-gray-400" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirm-password" className="block text-sm font-medium text-gray-700">
|
||||
パスワードの確認
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="confirm-password"
|
||||
name="confirm-password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
パスワードをリセット
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{message && (
|
||||
<div className="mt-6 text-center text-sm text-gray-500">
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordReset;
|
||||
156
rog/templates/password_reset.html
Normal file
156
rog/templates/password_reset.html
Normal file
@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>パスワードのリセット</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #0056b3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #003d82;
|
||||
}
|
||||
.message {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.message.error {
|
||||
background-color: #ffe6e6;
|
||||
color: #d8000c;
|
||||
}
|
||||
.message.success {
|
||||
background-color: #e6ffe6;
|
||||
color: #006400;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>岐阜ナビ:パスワードのリセット</h1>
|
||||
<form id="reset-form">
|
||||
<div class="form-group">
|
||||
<label for="password">新しいパスワード</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm-password">パスワードの確認</label>
|
||||
<input type="password" id="confirm-password" name="confirm-password" required>
|
||||
</div>
|
||||
<button type="submit">パスワードをリセット</button>
|
||||
</form>
|
||||
<div id="message" class="message"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
function extractParams(url) {
|
||||
const regex = /\/reset-password\/([^\/]+)\/([^\/]+)\/?/;
|
||||
const match = url.match(regex);
|
||||
if (match) {
|
||||
return { uidb64: match[1], token: match[2] };
|
||||
}
|
||||
const urlParams = new URLSearchParams(new URL(url).search);
|
||||
return {
|
||||
uidb64: urlParams.get('uidb64') || '',
|
||||
token: urlParams.get('token') || ''
|
||||
};
|
||||
}
|
||||
|
||||
const { uidb64, token } = extractParams(currentUrl);
|
||||
|
||||
document.getElementById('reset-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const password = document.getElementById('password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
const messageElement = document.getElementById('message');
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
messageElement.textContent = 'パスワードが一致しません。';
|
||||
messageElement.className = 'message error';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!uidb64 || !token) {
|
||||
messageElement.textContent = '無効なリセットリンクです。';
|
||||
messageElement.className = 'message error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/reset-password/${uidb64}/${token}/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
new_password: password,
|
||||
confirm_password: confirmPassword
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
messageElement.textContent = 'パスワードが正常にリセットされました。';
|
||||
messageElement.className = 'message success';
|
||||
} else {
|
||||
messageElement.textContent = `パスワードのリセットに失敗しました。エラー: ${data.error || response.statusText}`;
|
||||
messageElement.className = 'message error';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
messageElement.textContent = 'エラーが発生しました。もう一度お試しください。';
|
||||
messageElement.className = 'message error';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
41
rog/templates/password_reset_invalid.html
Normal file
41
rog/templates/password_reset_invalid.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>無効なパスワードリセットリンク</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f3f4f6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
color: #1f2937;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p {
|
||||
color: #4b5563;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>無効なリンク</h1>
|
||||
<p>このパスワードリセットリンクは無効です。新しいリセットリンクを要求してください。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -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,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView
|
||||
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,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView
|
||||
from django.urls import path, include
|
||||
from knox import views as knox_views
|
||||
|
||||
@ -90,5 +90,7 @@ urlpatterns += [
|
||||
path('userdetail/<int:user_id>/',update_user_detail, name='update_user_detail'),
|
||||
path('activate-member/<int:user_id>/<int:team_id>/', ActivateMemberView.as_view(), name='activate-member'),
|
||||
path('activate-new-member/<uuid:verification_code>/<int:team_id>/', ActivateNewMemberView.as_view(), name='activate-new-member'),
|
||||
path('password-reset/', PasswordResetRequestView.as_view(), name='password_reset_request'),
|
||||
path('reset-password/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
|
||||
|
||||
]
|
||||
|
||||
@ -43,7 +43,14 @@ def send_verification_email(user, activation_link):
|
||||
share_send_email(subject,body,user.email)
|
||||
|
||||
|
||||
|
||||
def send_reset_password_email(email,activation_link):
|
||||
context = {
|
||||
'name': email,
|
||||
'activation_link': activation_link,
|
||||
}
|
||||
logger.info(f"send_reset_password_email : {context}")
|
||||
subject, body = load_email_template('reset_password_email.txt', context)
|
||||
share_send_email(subject,body,email)
|
||||
|
||||
|
||||
# 既にユーザーになっている人にチームへの参加要請メールを出す。
|
||||
|
||||
87
rog/views.py
87
rog/views.py
@ -2,12 +2,17 @@ from .models import JpnSubPerf # このインポート文をファイルの先
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
import traceback
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
|
||||
import requests
|
||||
from rest_framework import serializers
|
||||
from django.db import IntegrityError
|
||||
from django.urls import reverse
|
||||
from .utils import send_verification_email,send_invitation_email,send_team_join_email
|
||||
from .utils import send_verification_email,send_invitation_email,send_team_join_email,send_reset_password_email
|
||||
from django.conf import settings
|
||||
import uuid
|
||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||
@ -26,7 +31,7 @@ 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, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer
|
||||
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, PasswordResetRequestSerializer, PasswordResetConfirmSerializer
|
||||
from knox.models import AuthToken
|
||||
|
||||
from rest_framework import viewsets, generics, status
|
||||
@ -483,7 +488,6 @@ class LoginView(APIView):
|
||||
password = request.data.get('password')
|
||||
|
||||
# デバッグコード
|
||||
from django.contrib.auth.hashers import make_password, check_password
|
||||
user = CustomUser.objects.filter(email=email).first()
|
||||
if user:
|
||||
stored_hash = user.password
|
||||
@ -1699,12 +1703,17 @@ class TempUserRegistrationView(APIView):
|
||||
# 新規仮登録
|
||||
serializer = TempUserRegistrationSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
temp_user = serializer.save()
|
||||
# シリアライザのvalidated_dataからパスワードを取得
|
||||
password = serializer.validated_data.get('password')
|
||||
# パスワードをハッシュ化
|
||||
hashed_password = make_password(password)
|
||||
# ハッシュ化されたパスワードでTempUserを作成
|
||||
temp_user = serializer.save(password=hashed_password)
|
||||
|
||||
verification_code = uuid.uuid4()
|
||||
temp_user.verification_code = verification_code
|
||||
#password = serializer.validated_data.pop('password')
|
||||
#temp_user.set_password(password)
|
||||
temp_user.save()
|
||||
|
||||
verification_url = request.build_absolute_uri(
|
||||
reverse('verify-email', kwargs={'verification_code': verification_code})
|
||||
)
|
||||
@ -1736,6 +1745,8 @@ class VerifyEmailView(APIView):
|
||||
if temp_user.is_valid():
|
||||
user_data = {
|
||||
'email': temp_user.email,
|
||||
'is_rogaining':True, # ここでis_rogainingをTrueに設定
|
||||
'password':temp_user.password,
|
||||
'is_rogaining': temp_user.is_rogaining,
|
||||
'zekken_number': temp_user.zekken_number,
|
||||
'event_code': temp_user.event_code,
|
||||
@ -1752,11 +1763,13 @@ class VerifyEmailView(APIView):
|
||||
|
||||
try:
|
||||
# CustomUserを作成
|
||||
user = CustomUser.objects.create_user(
|
||||
email=user_data['email'],
|
||||
password=temp_user.password,
|
||||
**{k: v for k, v in user_data.items() if k != 'email'}
|
||||
)
|
||||
user = CustomUser.objects.create(**user_data)
|
||||
|
||||
#user = CustomUser.objects.create_user(
|
||||
# email=user_data['email'],
|
||||
# password=temp_user.password, # ハッシュ化されたパスワードを直接使用
|
||||
# **{k: v for k, v in user_data.items() if k != 'email'}
|
||||
#)
|
||||
except ValidationError as e:
|
||||
# パスワードのバリデーションエラーなどの処理
|
||||
return render(request, 'verification_error.html', {'message': str(e), 'title': 'エラー'})
|
||||
@ -1795,3 +1808,55 @@ class TeamMembersWithUserView(generics.ListAPIView):
|
||||
team_id = self.kwargs['team_id']
|
||||
return Member.objects.filter(team_id=team_id).select_related('user', 'team')
|
||||
|
||||
|
||||
class PasswordResetRequestView(APIView):
|
||||
def post(self, request):
|
||||
serializer = PasswordResetRequestSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
email = serializer.validated_data['email']
|
||||
user = CustomUser.objects.filter(email=email).first()
|
||||
if user:
|
||||
token = default_token_generator.make_token(user)
|
||||
uid = urlsafe_base64_encode(force_bytes(user.pk))
|
||||
reset_link = f"{settings.FRONTEND_URL}/api/reset-password/{uid}/{token}/"
|
||||
send_reset_password_email(email,reset_link)
|
||||
|
||||
return Response({"message": "Password reset email sent"}, status=status.HTTP_200_OK)
|
||||
return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
class PasswordResetConfirmView(APIView):
|
||||
def get(self, request, uidb64, token):
|
||||
try:
|
||||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||||
user = CustomUser.objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
|
||||
user = None
|
||||
|
||||
if user is not None and default_token_generator.check_token(user, token):
|
||||
return render(request, 'password_reset.html', {'uid': uidb64, 'token': token})
|
||||
else:
|
||||
return render(request, 'password_reset_invalid.html')
|
||||
|
||||
if user is not None and default_token_generator.check_token(user, token):
|
||||
return Response({"message": "Token is valid"}, status=status.HTTP_200_OK)
|
||||
return Response({"message": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def post(self, request, uidb64, token):
|
||||
try:
|
||||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||||
user = CustomUser.objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
|
||||
return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if default_token_generator.check_token(user, token):
|
||||
serializer = PasswordResetConfirmSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user.set_password(serializer.validated_data['new_password'])
|
||||
user.save()
|
||||
return Response({"message": "Password has been reset successfully"}, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user