feat: Add comprehensive Django user app with tests using model-bakery
- Implemented user authentication with JWT tokens - Added user and profile models with OneToOne relationship - Created service layer for business logic separation - Implemented DRF serializers and views - Added comprehensive test suite with model-bakery factories - Fixed ipdb/pdbpp dependency conflicts with custom test runner - Configured development and production environments - Added deployment configurations for Apache, systemd, and Docker
This commit is contained in:
312
src/apps/user/views.py
Normal file
312
src/apps/user/views.py
Normal file
@@ -0,0 +1,312 @@
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.hashers import check_password
|
||||
from rest_framework import status, generics, permissions
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
from .models import User
|
||||
from .services import UserService, ProfileService
|
||||
from .serializers import (
|
||||
UserRegistrationSerializer,
|
||||
UserSerializer,
|
||||
LoginSerializer,
|
||||
TokenSerializer,
|
||||
PasswordChangeSerializer,
|
||||
UserUpdateSerializer,
|
||||
ProfileUpdateSerializer
|
||||
)
|
||||
|
||||
|
||||
class RegisterView(APIView):
|
||||
"""Регистрация нового пользователя"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=UserRegistrationSerializer,
|
||||
responses={201: UserSerializer}
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = UserRegistrationSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
# Убираем password_confirm из данных для создания пользователя
|
||||
user_data = serializer.validated_data.copy()
|
||||
user_data.pop('password_confirm', None)
|
||||
|
||||
user = UserService.create_user(**user_data)
|
||||
user_serializer = UserSerializer(user)
|
||||
tokens = UserService.get_tokens_for_user(user)
|
||||
|
||||
return Response({
|
||||
'user': user_serializer.data,
|
||||
'tokens': tokens
|
||||
}, status=status.HTTP_201_CREATED)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class LoginView(APIView):
|
||||
"""Вход пользователя"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=LoginSerializer,
|
||||
responses={200: TokenSerializer}
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = LoginSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
email = serializer.validated_data['email']
|
||||
password = serializer.validated_data['password']
|
||||
|
||||
user = authenticate(email=email, password=password)
|
||||
if user:
|
||||
tokens = UserService.get_tokens_for_user(user)
|
||||
return Response(tokens, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{'error': 'Неверные учетные данные'},
|
||||
status=status.HTTP_401_UNAUTHORIZED
|
||||
)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
"""Выход пользователя"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
],
|
||||
responses={200: 'Успешный выход'}
|
||||
)
|
||||
def post(self, request):
|
||||
try:
|
||||
refresh_token = request.data.get('refresh')
|
||||
if refresh_token:
|
||||
token = RefreshToken(refresh_token)
|
||||
token.blacklist()
|
||||
return Response({'message': 'Успешный выход'}, status=status.HTTP_200_OK)
|
||||
except Exception:
|
||||
return Response({'error': 'Неверный токен'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class CurrentUserView(APIView):
|
||||
"""Получение данных текущего пользователя"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
],
|
||||
responses={200: UserSerializer}
|
||||
)
|
||||
def get(self, request):
|
||||
serializer = UserSerializer(request.user)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class UserUpdateView(APIView):
|
||||
"""Обновление данных пользователя"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=UserUpdateSerializer,
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
],
|
||||
responses={200: UserSerializer}
|
||||
)
|
||||
def patch(self, request):
|
||||
serializer = UserUpdateSerializer(request.user, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
user = UserService.update_user(
|
||||
request.user.id,
|
||||
**serializer.validated_data
|
||||
)
|
||||
if user:
|
||||
user_serializer = UserSerializer(user)
|
||||
return Response(user_serializer.data)
|
||||
return Response(
|
||||
{'error': 'Пользователь не найден'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class ProfileDetailView(generics.RetrieveUpdateAPIView):
|
||||
"""Получение и обновление профиля пользователя"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ProfileUpdateSerializer
|
||||
|
||||
def get_object(self):
|
||||
profile = ProfileService.get_profile_by_user_id(self.request.user.id)
|
||||
if not profile:
|
||||
# Если профиль не существует, создаем его
|
||||
from .models import Profile
|
||||
profile = Profile.objects.create(user=self.request.user)
|
||||
return profile
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
]
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
profile = self.get_object()
|
||||
serializer = self.get_serializer(profile)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=ProfileUpdateSerializer,
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
]
|
||||
)
|
||||
def patch(self, request, *args, **kwargs):
|
||||
profile = self.get_object()
|
||||
serializer = self.get_serializer(profile, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
updated_profile = ProfileService.update_profile(
|
||||
request.user.id,
|
||||
**serializer.validated_data
|
||||
)
|
||||
if updated_profile:
|
||||
return Response(ProfileUpdateSerializer(updated_profile).data)
|
||||
return Response(
|
||||
{'error': 'Профиль не найден'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class PasswordChangeView(APIView):
|
||||
"""Смена пароля"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=PasswordChangeSerializer,
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'Authorization',
|
||||
openapi.IN_HEADER,
|
||||
description="Bearer <token>",
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True
|
||||
)
|
||||
],
|
||||
responses={200: 'Пароль успешно изменен'}
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = PasswordChangeSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user = request.user
|
||||
old_password = serializer.validated_data['old_password']
|
||||
|
||||
if check_password(old_password, user.password):
|
||||
new_password = serializer.validated_data['new_password']
|
||||
user.set_password(new_password)
|
||||
user.save()
|
||||
return Response(
|
||||
{'message': 'Пароль успешно изменен'},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
{'error': 'Неверный старый пароль'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def user_profile_detail(request):
|
||||
"""Получение полных данных профиля пользователя"""
|
||||
profile_data = ProfileService.get_full_profile_data(request.user.id)
|
||||
if profile_data:
|
||||
return Response(profile_data)
|
||||
return Response(
|
||||
{'error': 'Профиль не найден'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
class TokenRefreshView(APIView):
|
||||
"""Обновление access токена через refresh токен"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'refresh': openapi.Schema(type=openapi.TYPE_STRING, description='Refresh token')
|
||||
},
|
||||
required=['refresh']
|
||||
),
|
||||
responses={200: TokenSerializer}
|
||||
)
|
||||
def post(self, request):
|
||||
refresh_token = request.data.get('refresh')
|
||||
if not refresh_token:
|
||||
return Response(
|
||||
{'error': 'Refresh token обязателен'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
refresh = RefreshToken(refresh_token)
|
||||
return Response({
|
||||
'access': str(refresh.access_token),
|
||||
'refresh': str(refresh)
|
||||
})
|
||||
except Exception:
|
||||
return Response(
|
||||
{'error': 'Неверный refresh token'},
|
||||
status=status.HTTP_401_UNAUTHORIZED
|
||||
)
|
||||
Reference in New Issue
Block a user