mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Update password function
This commit is contained in:
parent
47564c9d91
commit
1aa051e8ff
|
@ -70,6 +70,17 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
'last_name',
|
'last_name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class ChangePasswordSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for password change endpoint.
|
||||||
|
"""
|
||||||
|
old_password = serializers.CharField(required=True)
|
||||||
|
new_password = serializers.CharField(required=True)
|
||||||
|
|
||||||
|
# def validate(self, attrs):
|
||||||
|
# if attrs['new_password'] != "123":
|
||||||
|
# raise serializers.ValidationError({"password": r"Пароль не '123'"})
|
||||||
|
# return attrs
|
||||||
|
|
||||||
class SignupSerializer(serializers.ModelSerializer):
|
class SignupSerializer(serializers.ModelSerializer):
|
||||||
''' User profile create '''
|
''' User profile create '''
|
||||||
|
|
|
@ -10,4 +10,5 @@ urlpatterns = [
|
||||||
path('api/signup', views.SignupAPIView.as_view()),
|
path('api/signup', views.SignupAPIView.as_view()),
|
||||||
path('api/login', views.LoginAPIView.as_view()),
|
path('api/login', views.LoginAPIView.as_view()),
|
||||||
path('api/logout', views.LogoutAPIView.as_view()),
|
path('api/logout', views.LogoutAPIView.as_view()),
|
||||||
|
path('api/change-password', views.UpdatePassword.as_view()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework.response import Response
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
class LoginAPIView(views.APIView):
|
class LoginAPIView(views.APIView):
|
||||||
'''
|
'''
|
||||||
|
@ -74,3 +75,32 @@ class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
class UpdatePassword(views.APIView):
|
||||||
|
"""
|
||||||
|
An endpoint for changing password.
|
||||||
|
"""
|
||||||
|
# {"username": "admin", "password": "1234"}
|
||||||
|
# {"old_password": "1234", "new_password": "1234"}
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
serializer = serializers.ChangePasswordSerializer(data=request.data)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
# Check old password
|
||||||
|
old_password = serializer.data.get("old_password")
|
||||||
|
if not self.object.check_password(old_password):
|
||||||
|
return Response({"old_password": ["Wrong password."]},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
# set_password also hashes the password that the user will get
|
||||||
|
self.object.set_password(serializer.data.get("new_password"))
|
||||||
|
self.object.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Route, Routes } from 'react-router-dom';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import Navigation from './components/Navigation/Navigation';
|
import Navigation from './components/Navigation/Navigation';
|
||||||
import ToasterThemed from './components/ToasterThemed';
|
import ToasterThemed from './components/ToasterThemed';
|
||||||
|
import { useConceptTheme } from './context/ThemeContext';
|
||||||
import CreateRSFormPage from './pages/CreateRSFormPage';
|
import CreateRSFormPage from './pages/CreateRSFormPage';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import LibraryPage from './pages/LibraryPage';
|
import LibraryPage from './pages/LibraryPage';
|
||||||
|
@ -15,6 +16,9 @@ import RSFormPage from './pages/RSFormPage';
|
||||||
import UserProfilePage from './pages/UserProfilePage';
|
import UserProfilePage from './pages/UserProfilePage';
|
||||||
|
|
||||||
function App () {
|
function App () {
|
||||||
|
const {noNavigation} = useConceptTheme()
|
||||||
|
const nav_height: string = noNavigation ? "7.5rem" : "7.4rem";
|
||||||
|
const main_clsN: string = `min-h-[calc(100vh-${nav_height})] px-2 h-fit`;
|
||||||
return (
|
return (
|
||||||
<div className='antialiased clr-app'>
|
<div className='antialiased clr-app'>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
@ -24,7 +28,7 @@ function App () {
|
||||||
draggable={false}
|
draggable={false}
|
||||||
pauseOnFocusLoss={false}
|
pauseOnFocusLoss={false}
|
||||||
/>
|
/>
|
||||||
<main className='min-h-[calc(100vh-7.5rem)] px-2 h-fit'>
|
<main className={main_clsN}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={ <HomePage/>} />
|
<Route path='/' element={ <HomePage/>} />
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI';
|
import { DataCallback, getProfile, patchPassword,patchProfile } from '../utils/backendAPI';
|
||||||
import { IUserProfile, IUserUpdateData } from '../utils/models';
|
import { IUserProfile, IUserUpdateData, IUserUpdatePassword } from '../utils/models';
|
||||||
|
import { useAuth } from './AuthContext';
|
||||||
|
|
||||||
interface IUserProfileContextContext {
|
interface IUserProfileContext {
|
||||||
user: IUserProfile | undefined
|
user: IUserProfile | undefined
|
||||||
loading: boolean
|
loading: boolean
|
||||||
processing: boolean
|
processing: boolean
|
||||||
error: ErrorInfo
|
error: ErrorInfo
|
||||||
setError: (error: ErrorInfo) => void
|
setError: (error: ErrorInfo) => void
|
||||||
updateUser: (data: IUserUpdateData, callback?: DataCallback<IUserProfile>) => void
|
updateUser: (data: IUserUpdateData, callback?: DataCallback<IUserProfile>) => void
|
||||||
|
updatePassword: (data: IUserUpdatePassword, callback?: () => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileContext = createContext<IUserProfileContextContext | null>(null);
|
const ProfileContext = createContext<IUserProfileContext | null>(null);
|
||||||
|
|
||||||
export const useUserProfile = () => {
|
export const useUserProfile = () => {
|
||||||
const context = useContext(ProfileContext);
|
const context = useContext(ProfileContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
@ -33,6 +36,7 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
const auth = useAuth()
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
() => {
|
() => {
|
||||||
|
@ -63,13 +67,29 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updatePassword = useCallback(
|
||||||
|
(data: IUserUpdatePassword, callback?: () => void) => {
|
||||||
|
setError(undefined);
|
||||||
|
patchPassword({
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error); },
|
||||||
|
onSuccess: () => {
|
||||||
|
setUser(undefined);
|
||||||
|
auth.logout();
|
||||||
|
if (callback) callback();
|
||||||
|
}});
|
||||||
|
}, [setUser, auth]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
}, [reload]);
|
}, [reload]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileContext.Provider
|
<ProfileContext.Provider
|
||||||
value={{user, updateUser, error, loading, setError, processing}}
|
value={{user, updateUser, updatePassword, error, loading, setError, processing}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ProfileContext.Provider>
|
</ProfileContext.Provider>
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import TextInput from '../../components/Common/TextInput';
|
||||||
|
import { useUserProfile } from '../../context/UserProfileContext';
|
||||||
|
import { IUserUpdatePassword } from '../../utils/models';
|
||||||
|
|
||||||
|
|
||||||
|
export function ChangePassword() {
|
||||||
|
const { updatePassword, processing } = useUserProfile();
|
||||||
|
|
||||||
|
const [old_password, setOldPassword] = useState('');
|
||||||
|
const [new_password, setNewPassword] = useState('');
|
||||||
|
const [new_password_repeat, setNewPasswordRepeat] = useState('');
|
||||||
|
const [password_equal, setPasswordEqual] = useState(true);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const input_class: string = `flex-grow max-w-xl px-3 py-2 border center min-w-full ${password_equal ? "" : "text-red-500"}`
|
||||||
|
|
||||||
|
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (new_password !== new_password_repeat) {
|
||||||
|
setPasswordEqual(false);
|
||||||
|
toast.error('Пароли не совпадают');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const data: IUserUpdatePassword = {
|
||||||
|
old_password: old_password,
|
||||||
|
new_password: new_password,
|
||||||
|
};
|
||||||
|
updatePassword(data, () => {toast.success('Изменения сохранены'); navigate('/login')});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex-grow max-w-sm px-4'>
|
||||||
|
<form onSubmit={handleSubmit} className='flex-grow min-h-full px-6 py-2 border min-w-fit bg-slate-200'>
|
||||||
|
<TextInput id='old_password'
|
||||||
|
type='password'
|
||||||
|
label='Введите старый пароль:'
|
||||||
|
value={old_password}
|
||||||
|
onChange={event => (setOldPassword(event.target.value))}
|
||||||
|
/>
|
||||||
|
<TextInput id='new_password'
|
||||||
|
className={input_class}
|
||||||
|
label="Введите новый пароль:"
|
||||||
|
value={new_password}
|
||||||
|
onChange={event => (setNewPassword(event.target.value), setPasswordEqual(true))}
|
||||||
|
/>
|
||||||
|
<TextInput id='new_password'
|
||||||
|
className={input_class}
|
||||||
|
label="Повторите новый пароль:"
|
||||||
|
value={new_password_repeat}
|
||||||
|
onChange={event => (setNewPasswordRepeat(event.target.value), setPasswordEqual(true))}
|
||||||
|
/>
|
||||||
|
<div className='relative flex justify-center my-4 border'>
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='absolute bottom-0 px-2 py-1 bg-blue-500 border'
|
||||||
|
disabled={processing}>
|
||||||
|
<span>Сменить пароль</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
|
@ -4,15 +4,20 @@ import { toast } from 'react-toastify';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../components/Common/TextInput';
|
||||||
import { useUserProfile } from '../../context/UserProfileContext';
|
import { useUserProfile } from '../../context/UserProfileContext';
|
||||||
import { IUserUpdateData } from '../../utils/models';
|
import { IUserUpdateData } from '../../utils/models';
|
||||||
|
import { ChangePassword } from './ChangePassword';
|
||||||
|
|
||||||
|
|
||||||
export function UserProfile() {
|
export function UserProfile() {
|
||||||
const { updateUser, user, processing } = useUserProfile();
|
const { updateUser, user, processing, error } = useUserProfile();
|
||||||
|
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [first_name, setFirstName] = useState('');
|
const [first_name, setFirstName] = useState('');
|
||||||
const [last_name, setLastName] = useState('');
|
const [last_name, setLastName] = useState('');
|
||||||
|
|
||||||
|
|
||||||
|
// const [showChangePassword, setShowChangePassword] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -35,8 +40,10 @@ export function UserProfile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
<div><h1 className='flex justify-center py-2'> Учетные данные пользователя </h1>
|
||||||
<div className='flex flex-col items-center justify-center px-2 py-2 border'>
|
<div className='flex flex-row justify-center py-2'>
|
||||||
|
<div className='flex-grow max-w-sm px-4 '>
|
||||||
|
<form onSubmit={handleSubmit} className='flex-grow px-6 py-2 border center min-w-fit'>
|
||||||
<TextInput id='username'
|
<TextInput id='username'
|
||||||
label='Логин:'
|
label='Логин:'
|
||||||
value={username}
|
value={username}
|
||||||
|
@ -49,15 +56,18 @@ export function UserProfile() {
|
||||||
/>
|
/>
|
||||||
<TextInput id='last_name' label="Фамилия:" value={last_name} onChange={event => setLastName(event.target.value)}/>
|
<TextInput id='last_name' label="Фамилия:" value={last_name} onChange={event => setLastName(event.target.value)}/>
|
||||||
<TextInput id='email' label="Электронная почта:" value={email} onChange={event => setEmail(event.target.value)}/>
|
<TextInput id='email' label="Электронная почта:" value={email} onChange={event => setEmail(event.target.value)}/>
|
||||||
<div className='flex items-center justify-between my-4'>
|
<div className='flex items-center justify-center my-4'>
|
||||||
<button
|
<button
|
||||||
type='submit'
|
type='submit'
|
||||||
className='px-2 py-1 bg-green-500 border'
|
className='px-2 py-1 bg-green-500 border center'
|
||||||
disabled={processing}>
|
disabled={processing}>
|
||||||
<span>Сохранить</span>
|
<span>Сохранить мои данные</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<ChangePassword />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
)}
|
)}
|
|
@ -9,7 +9,7 @@ import {
|
||||||
ICurrentUser, IExpressionParse, IRSExpression,
|
ICurrentUser, IExpressionParse, IRSExpression,
|
||||||
IRSFormCreateData, IRSFormData,
|
IRSFormCreateData, IRSFormData,
|
||||||
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
|
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
|
||||||
IUserLoginData, IUserProfile, IUserSignupData, IUserUpdateData
|
IUserLoginData, IUserProfile, IUserSignupData, IUserUpdateData, IUserUpdatePassword
|
||||||
} from './models'
|
} from './models'
|
||||||
|
|
||||||
export function initBackend() {
|
export function initBackend() {
|
||||||
|
@ -101,6 +101,14 @@ export function patchProfile(request: FrontExchange<IUserUpdateData, IUserProfil
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function patchPassword(request: FrontPush<IUserUpdatePassword>) {
|
||||||
|
AxiosPatch({
|
||||||
|
title: 'Update Password',
|
||||||
|
endpoint: '/users/api/change-password',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||||
AxiosGet({
|
AxiosGet({
|
||||||
title: 'Active users list',
|
title: 'Active users list',
|
||||||
|
|
|
@ -25,6 +25,11 @@ export interface IUserUpdateData extends Omit<IUser, 'is_staff' | 'id'> {}
|
||||||
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||||
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||||
|
|
||||||
|
export interface IUserUpdatePassword {
|
||||||
|
old_password: string
|
||||||
|
new_password: string
|
||||||
|
}
|
||||||
|
|
||||||
// ======== RS Parsing ============
|
// ======== RS Parsing ============
|
||||||
export enum Syntax {
|
export enum Syntax {
|
||||||
UNDEF = 'undefined',
|
UNDEF = 'undefined',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user