Release 4-8-6
This commit is contained in:
@ -71,7 +71,7 @@ ROOT_URLCONF = 'config.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [BASE_DIR / 'templates'],
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@ -176,8 +176,8 @@ REST_FRAMEWORK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
|
FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||||
FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
|
#FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
|
||||||
|
|
||||||
# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。
|
# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|||||||
@ -155,17 +155,22 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
|
|||||||
# return user
|
# return user
|
||||||
|
|
||||||
class TempUserRegistrationSerializer(serializers.ModelSerializer):
|
class TempUserRegistrationSerializer(serializers.ModelSerializer):
|
||||||
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TempUser
|
model = TempUser
|
||||||
fields = ('email', 'firstname', 'lastname', 'date_of_birth', 'female')
|
fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female')
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['verification_code'] = str(uuid.uuid4())
|
# パスワードのハッシュ化はviewで行うので、ここではそのまま保存
|
||||||
raw_password = validated_data.get('password')
|
|
||||||
hashed_password = make_password(raw_password)
|
|
||||||
validated_data['password'] = hashed_password
|
|
||||||
return TempUser.objects.create(**validated_data)
|
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 UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -793,4 +798,16 @@ class EntryCreationSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
return entry
|
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 sys import prefix
|
||||||
from rest_framework import urlpatterns
|
from rest_framework import urlpatterns
|
||||||
from rest_framework.routers import DefaultRouter
|
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 django.urls import path, include
|
||||||
from knox import views as knox_views
|
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('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-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('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)
|
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
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
import traceback
|
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
|
import requests
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.urls import reverse
|
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
|
from django.conf import settings
|
||||||
import uuid
|
import uuid
|
||||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||||
@ -26,7 +31,7 @@ from curses.ascii import NUL
|
|||||||
from django.core.serializers import serialize
|
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 .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 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 knox.models import AuthToken
|
||||||
|
|
||||||
from rest_framework import viewsets, generics, status
|
from rest_framework import viewsets, generics, status
|
||||||
@ -483,7 +488,6 @@ class LoginView(APIView):
|
|||||||
password = request.data.get('password')
|
password = request.data.get('password')
|
||||||
|
|
||||||
# デバッグコード
|
# デバッグコード
|
||||||
from django.contrib.auth.hashers import make_password, check_password
|
|
||||||
user = CustomUser.objects.filter(email=email).first()
|
user = CustomUser.objects.filter(email=email).first()
|
||||||
if user:
|
if user:
|
||||||
stored_hash = user.password
|
stored_hash = user.password
|
||||||
@ -1699,12 +1703,17 @@ class TempUserRegistrationView(APIView):
|
|||||||
# 新規仮登録
|
# 新規仮登録
|
||||||
serializer = TempUserRegistrationSerializer(data=request.data)
|
serializer = TempUserRegistrationSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
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()
|
verification_code = uuid.uuid4()
|
||||||
temp_user.verification_code = verification_code
|
temp_user.verification_code = verification_code
|
||||||
#password = serializer.validated_data.pop('password')
|
|
||||||
#temp_user.set_password(password)
|
|
||||||
temp_user.save()
|
temp_user.save()
|
||||||
|
|
||||||
verification_url = request.build_absolute_uri(
|
verification_url = request.build_absolute_uri(
|
||||||
reverse('verify-email', kwargs={'verification_code': verification_code})
|
reverse('verify-email', kwargs={'verification_code': verification_code})
|
||||||
)
|
)
|
||||||
@ -1736,6 +1745,8 @@ class VerifyEmailView(APIView):
|
|||||||
if temp_user.is_valid():
|
if temp_user.is_valid():
|
||||||
user_data = {
|
user_data = {
|
||||||
'email': temp_user.email,
|
'email': temp_user.email,
|
||||||
|
'is_rogaining':True, # ここでis_rogainingをTrueに設定
|
||||||
|
'password':temp_user.password,
|
||||||
'is_rogaining': temp_user.is_rogaining,
|
'is_rogaining': temp_user.is_rogaining,
|
||||||
'zekken_number': temp_user.zekken_number,
|
'zekken_number': temp_user.zekken_number,
|
||||||
'event_code': temp_user.event_code,
|
'event_code': temp_user.event_code,
|
||||||
@ -1752,11 +1763,13 @@ class VerifyEmailView(APIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# CustomUserを作成
|
# CustomUserを作成
|
||||||
user = CustomUser.objects.create_user(
|
user = CustomUser.objects.create(**user_data)
|
||||||
email=user_data['email'],
|
|
||||||
password=temp_user.password,
|
#user = CustomUser.objects.create_user(
|
||||||
**{k: v for k, v in user_data.items() if k != 'email'}
|
# email=user_data['email'],
|
||||||
)
|
# password=temp_user.password, # ハッシュ化されたパスワードを直接使用
|
||||||
|
# **{k: v for k, v in user_data.items() if k != 'email'}
|
||||||
|
#)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
# パスワードのバリデーションエラーなどの処理
|
# パスワードのバリデーションエラーなどの処理
|
||||||
return render(request, 'verification_error.html', {'message': str(e), 'title': 'エラー'})
|
return render(request, 'verification_error.html', {'message': str(e), 'title': 'エラー'})
|
||||||
@ -1795,3 +1808,55 @@ class TeamMembersWithUserView(generics.ListAPIView):
|
|||||||
team_id = self.kwargs['team_id']
|
team_id = self.kwargs['team_id']
|
||||||
return Member.objects.filter(team_id=team_id).select_related('user', 'team')
|
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