Refactor project structure and update configurations for State Corp backend

- Updated project description in __init__.py
- Enhanced .gitignore to exclude additional data files
- Modified User model to remove first_name and last_name fields
- Improved instance save method in services.py to include updated_at field
- Added API tokens to .env.example for external services
- Cleaned up test files for better readability
- Updated Dockerfile and docker-compose.yml for improved setup
- Revised README.md to reflect project changes and added changelog
This commit is contained in:
2026-02-17 09:24:42 +01:00
parent e9d7f24aaa
commit fd2adf9ab4
31 changed files with 1419 additions and 933 deletions

View File

@@ -6,6 +6,10 @@ from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""Расширенная модель пользователя"""
# Убираем first_name и last_name из модели User (они в Profile)
first_name = None
last_name = None
# Переопределяем группы и разрешения для избежания конфликта
groups = models.ManyToManyField(
"auth.Group",

View File

@@ -8,7 +8,6 @@ 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 .serializers import (
LoginSerializer,
PasswordChangeSerializer,
@@ -20,14 +19,26 @@ from .serializers import (
)
from .services import ProfileService, UserService
# Swagger теги для группировки
AUTH_TAG = "Аутентификация"
USER_TAG = "Пользователь"
class RegisterView(APIView):
"""Регистрация нового пользователя"""
"""
Регистрация нового пользователя.
Создаёт учётную запись и возвращает JWT токены.
"""
permission_classes = [AllowAny]
@swagger_auto_schema(
request_body=UserRegistrationSerializer, responses={201: UserSerializer}
tags=[AUTH_TAG],
operation_summary="Регистрация",
operation_description="Создание новой учётной записи пользователя.",
request_body=UserRegistrationSerializer,
responses={201: UserSerializer},
)
def post(self, request):
serializer = UserRegistrationSerializer(data=request.data)
@@ -49,11 +60,21 @@ class RegisterView(APIView):
class LoginView(APIView):
"""Вход пользователя"""
"""
Вход пользователя.
Возвращает access и refresh токены для авторизации.
"""
permission_classes = [AllowAny]
@swagger_auto_schema(request_body=LoginSerializer, responses={200: TokenSerializer})
@swagger_auto_schema(
tags=[AUTH_TAG],
operation_summary="Вход",
operation_description="Аутентификация по email и паролю. Возвращает JWT токены.",
request_body=LoginSerializer,
responses={200: TokenSerializer},
)
def post(self, request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
@@ -74,50 +95,35 @@ class LoginView(APIView):
class LogoutView(APIView):
"""Выход пользователя"""
"""
Выход пользователя.
Логаут на JWT означает удаление токенов на клиенте.
"""
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
"Authorization",
openapi.IN_HEADER,
description="Bearer <token>",
type=openapi.TYPE_STRING,
required=True,
)
],
tags=[AUTH_TAG],
operation_summary="Выход",
operation_description="Выход из системы (удаление токенов на клиенте).",
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
)
# Для JWT логаут означает удаление токенов на клиенте.
# Сервер не хранит сессию и ничего не инвалидирует.
return Response({"message": "Успешный выход"}, status=status.HTTP_200_OK)
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,
)
],
tags=[USER_TAG],
operation_summary="Текущий пользователь",
operation_description="Возвращает данные авторизованного пользователя.",
responses={200: UserSerializer},
)
def get(self, request):
@@ -126,21 +132,15 @@ class CurrentUserView(APIView):
class UserUpdateView(APIView):
"""Обновление данных пользователя"""
"""Обновление данных пользователя."""
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
tags=[USER_TAG],
operation_summary="Обновить данные",
operation_description="Частичное обновление данных пользователя.",
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):
@@ -153,7 +153,7 @@ class UserUpdateView(APIView):
class ProfileDetailView(generics.RetrieveUpdateAPIView):
"""Получение и обновление профиля пользователя"""
"""Получение и обновление профиля пользователя."""
permission_classes = [IsAuthenticated]
serializer_class = ProfileUpdateSerializer
@@ -168,15 +168,9 @@ class ProfileDetailView(generics.RetrieveUpdateAPIView):
return profile
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
"Authorization",
openapi.IN_HEADER,
description="Bearer <token>",
type=openapi.TYPE_STRING,
required=True,
)
]
tags=[USER_TAG],
operation_summary="Получить профиль",
operation_description="Возвращает профиль текущего пользователя.",
)
def get(self, request, *args, **kwargs):
profile = self.get_object()
@@ -184,16 +178,10 @@ class ProfileDetailView(generics.RetrieveUpdateAPIView):
return Response(serializer.data)
@swagger_auto_schema(
tags=[USER_TAG],
operation_summary="Обновить профиль",
operation_description="Частичное обновление профиля пользователя.",
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()
@@ -207,21 +195,15 @@ class ProfileDetailView(generics.RetrieveUpdateAPIView):
class PasswordChangeView(APIView):
"""Смена пароля"""
"""Смена пароля пользователя."""
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
tags=[USER_TAG],
operation_summary="Сменить пароль",
operation_description="Смена пароля. Требуется текущий пароль для подтверждения.",
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):
@@ -246,20 +228,29 @@ class PasswordChangeView(APIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
method="get",
tags=[USER_TAG],
operation_summary="Полный профиль",
operation_description="Расширенная информация о пользователе и профиле.",
)
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def user_profile_detail(request):
"""Получение полных данных профиля пользователя"""
"""Получение полных данных профиля пользователя."""
profile_data = ProfileService.get_full_profile_data(request.user.id)
return Response(profile_data)
class TokenRefreshView(APIView):
"""Обновление access токена через refresh токен"""
"""Обновление access токена через refresh токен."""
permission_classes = [AllowAny]
@swagger_auto_schema(
tags=[AUTH_TAG],
operation_summary="Обновить токен",
operation_description="Получение нового access токена по refresh токену.",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={